From 2f15543f913da5c9100ce114f4268c0576b70a3e Mon Sep 17 00:00:00 2001 From: Alex Ning Date: Mon, 22 Jul 2019 00:09:40 +0800 Subject: 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. --- .idea/caches/build_file_checksums.ser | Bin 533 -> 533 bytes .idea/caches/gradle_models.ser | Bin 254866 -> 254881 bytes .../infinityforreddit/AppComponent.java | 1 + .../CommentAndPostRecyclerViewAdapter.java | 2 +- .../infinityforreddit/CommentDataSource.java | 200 +++++++++++++++ .../CommentDataSourceFactory.java | 43 ++++ .../infinityforreddit/CommentViewModel.java | 83 ++++++ .../infinityforreddit/CommentsListingFragment.java | 149 +++++++++++ .../CommentsListingRecyclerViewAdapter.java | 284 +++++++++++++++++++++ .../infinityforreddit/JSONUtils.java | 27 +- .../infinityforreddit/ParseComment.java | 2 +- .../infinityforreddit/ParsePost.java | 19 +- .../infinityforreddit/PostDataSource.java | 20 +- .../infinityforreddit/RedditAPI.java | 7 +- .../infinityforreddit/SearchActivity.java | 9 + .../SubredditListingRecyclerViewAdapter.java | 2 +- .../UserListingRecyclerViewAdapter.java | 2 +- .../infinityforreddit/ViewUserDetailActivity.java | 150 ++++++++++- .../main/res/layout/activity_view_user_detail.xml | 20 +- .../main/res/layout/fragment_comments_listing.xml | 48 ++++ .../item_load_comments_failed_placeholder.xml | 2 +- app/src/main/res/values/strings.xml | 2 +- 22 files changed, 1020 insertions(+), 52 deletions(-) create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/CommentDataSource.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/CommentDataSourceFactory.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/CommentViewModel.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/CommentsListingFragment.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/CommentsListingRecyclerViewAdapter.java create mode 100644 app/src/main/res/layout/fragment_comments_listing.xml diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser index 35e47bf8..1c28698e 100644 Binary files a/.idea/caches/build_file_checksums.ser and b/.idea/caches/build_file_checksums.ser differ diff --git a/.idea/caches/gradle_models.ser b/.idea/caches/gradle_models.ser index 226d995d..cce3e832 100644 Binary files a/.idea/caches/gradle_models.ser and b/.idea/caches/gradle_models.ser differ 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 { + interface OnCommentFetchedCallback { + void hasComment(); + void noComment(); + } + + private Retrofit retrofit; + private Locale locale; + private String username; + private OnCommentFetchedCallback onCommentFetchedCallback; + + private MutableLiveData paginationNetworkStateLiveData; + private MutableLiveData initialLoadStateLiveData; + + private LoadInitialParams initialParams; + private LoadInitialCallback initialCallback; + private LoadParams params; + private LoadCallback 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 params, @NonNull LoadInitialCallback callback) { + initialParams = params; + initialCallback = callback; + + RedditAPI api = retrofit.create(RedditAPI.class); + Call bestPost = api.getUserComments(username, null); + bestPost.enqueue(new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + if(response.isSuccessful()) { + new ParseCommentAsyncTask(response.body(), locale, new ParseCommentAsyncTask.ParseCommentAsyncTaskListener() { + @Override + public void parseSuccessful(ArrayList 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 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 params, @NonNull LoadCallback callback) { + + } + + @Override + public void loadAfter(@NonNull LoadParams params, @NonNull LoadCallback callback) { + this.params = params; + this.callback = callback; + + RedditAPI api = retrofit.create(RedditAPI.class); + Call bestPost = api.getUserComments(username, params.key); + bestPost.enqueue(new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + if(response.isSuccessful()) { + new ParseCommentAsyncTask(response.body(), locale, new ParseCommentAsyncTask.ParseCommentAsyncTaskListener() { + @Override + public void parseSuccessful(ArrayList 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 call, @NonNull Throwable t) { + paginationNetworkStateLiveData.postValue(new NetworkState(NetworkState.Status.FAILED, "Error parsing data")); + } + }); + } + + private static class ParseCommentAsyncTask extends AsyncTask, ArrayList> { + private String response; + private String after; + private Locale locale; + private ParseCommentAsyncTaskListener parseCommentAsyncTaskListener; + + interface ParseCommentAsyncTaskListener { + void parseSuccessful(ArrayList comments, String after); + void parseFailed(); + } + + ParseCommentAsyncTask(String response, Locale locale, ParseCommentAsyncTaskListener parseCommentAsyncTaskListener) { + this.response = response; + this.locale = locale; + this.parseCommentAsyncTaskListener = parseCommentAsyncTaskListener; + } + + @Override + protected ArrayList doInBackground(Void... voids) { + try { + JSONObject data = new JSONObject(response).getJSONObject(JSONUtils.DATA_KEY); + JSONArray commentsJSONArray = data.getJSONArray(JSONUtils.CHILDREN_KEY); + ArrayList 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) { + 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 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 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 paginationNetworkState; + private LiveData initialLoadingState; + private LiveData> 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::getInitialLoadStateLiveData); + paginationNetworkState = Transformations.switchMap(commentDataSourceFactory.getCommentDataSourceLiveData(), + (Function>) CommentDataSource::getPaginationNetworkStateLiveData); + PagedList.Config pagedListConfig = + (new PagedList.Config.Builder()) + .setEnablePlaceholders(false) + .setPageSize(25) + .build(); + + comments = (new LivePagedListBuilder(commentDataSourceFactory, pagedListConfig)).build(); + } + + LiveData> getComments() { + return comments; + } + + LiveData getPaginationNetworkState() { + return paginationNetworkState; + } + + LiveData 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 create(@NonNull Class 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 { + 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 DIFF_CALLBACK = new DiffUtil.ItemCallback() { + @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 { ParsePost.parsePosts(response.body(), locale, -1, new ParsePost.ParsePostsListingListener() { @Override public void onParsePostsListingSuccess(ArrayList 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 { ParsePost.parsePosts(response.body(), locale, -1, new ParsePost.ParsePostsListingListener() { @Override public void onParsePostsListingSuccess(ArrayList 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 { Call 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 { ParsePost.parsePosts(response.body(), locale, -1, new ParsePost.ParsePostsListingListener() { @Override public void onParsePostsListingSuccess(ArrayList 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 getSubredditBestPosts(@Path("subredditName") String subredditName, @Query("after") String lastItem, @HeaderMap Map headers); - @GET("user/{userName}.json?raw_json=1&limit=25") - Call getUserBestPosts(@Path("userName") String userName, @Query("after") String lastItem, + @GET("user/{username}/submitted.json?raw_json=1&limit=25") + Call getUserBestPosts(@Path("username") String username, @Query("after") String lastItem, @HeaderMap Map headers); @GET("user/{username}/about.json?raw_json=1") Call getUserData(@Path("username") String username); + @GET("user/{username}/comments.json?raw_json=1") + Call getUserComments(@Path("username") String username, @Query("after") String after); + @FormUrlEncoded @POST("api/subscribe") Call subredditSubscription(@HeaderMap Map headers, @FieldMap Map 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; @@ -98,6 +99,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)) { 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 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 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 { 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"> + + + + - + \ 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 @@ + + + + + + + + + + + + + + + + \ 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"/> \ 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 @@ No storage permission to save this file Error loading posts - Error loading comments + Error loading comments Retry Comments No comments yet. Write a comment? -- cgit v1.2.3