From 3899b9adfad97823d99efcfd7b4a02dfb23fa372 Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Fri, 18 Dec 2020 15:45:17 -0500 Subject: [PATCH] comments viewer improvement 1. Viewing parent comments is now paginated, no more long waits 2. Liking a comment will no longer refresh the entire comment list 3. The pink tint on liked comments from v18 is restored also removed FeedStoriesFetcher which was deprecated by c24fd01 --- .../adapters/CommentsAdapter.java | 16 +++- .../comments/ChildCommentViewHolder.java | 3 +- .../comments/ParentCommentViewHolder.java | 3 +- .../instagrabber/asyncs/CommentsFetcher.java | 52 +++++------ .../asyncs/FeedStoriesFetcher.java | 93 ------------------- .../fragments/CommentsViewerFragment.java | 70 +++++++++++--- .../instagrabber/models/CommentModel.java | 10 +- app/src/main/res/values/color.xml | 1 + 8 files changed, 106 insertions(+), 142 deletions(-) delete mode 100755 app/src/main/java/awais/instagrabber/asyncs/FeedStoriesFetcher.java diff --git a/app/src/main/java/awais/instagrabber/adapters/CommentsAdapter.java b/app/src/main/java/awais/instagrabber/adapters/CommentsAdapter.java index e0ccb44e..d88e5f34 100755 --- a/app/src/main/java/awais/instagrabber/adapters/CommentsAdapter.java +++ b/app/src/main/java/awais/instagrabber/adapters/CommentsAdapter.java @@ -83,8 +83,8 @@ public final class CommentsAdapter extends ListAdapter> { private static final String TAG = "CommentsFetcher"; - private final String shortCode; + private final String shortCode, endCursor; private final FetchListener> fetchListener; - public CommentsFetcher(final String shortCode, final FetchListener> fetchListener) { + public CommentsFetcher(final String shortCode, final String endCursor, final FetchListener> fetchListener) { this.shortCode = shortCode; + this.endCursor = endCursor; this.fetchListener = fetchListener; } @@ -48,15 +49,17 @@ public final class CommentsFetcher extends AsyncTask commentModels = getParentComments(); - for (final CommentModel commentModel : commentModels) { - final List childCommentModels = commentModel.getChildCommentModels(); - if (childCommentModels != null) { - final int childCommentsLen = childCommentModels.size(); - final CommentModel lastChild = childCommentModels.get(childCommentsLen - 1); - if (lastChild != null && lastChild.hasNextPage() && !TextUtils.isEmpty(lastChild.getEndCursor())) { - final List remoteChildComments = getChildComments(commentModel.getId()); - commentModel.setChildCommentModels(remoteChildComments); - lastChild.setPageCursor(false, null); + if (commentModels != null) { + for (final CommentModel commentModel : commentModels) { + final List childCommentModels = commentModel.getChildCommentModels(); + if (childCommentModels != null) { + final int childCommentsLen = childCommentModels.size(); + final CommentModel lastChild = childCommentModels.get(childCommentsLen - 1); + if (lastChild != null && lastChild.hasNextPage() && !TextUtils.isEmpty(lastChild.getEndCursor())) { + final List remoteChildComments = getChildComments(commentModel.getId()); + commentModel.setChildCommentModels(remoteChildComments); + lastChild.setPageCursor(false, null); + } } } } @@ -76,11 +79,10 @@ public final class CommentsFetcher extends AsyncTask getChildComments(final String commentId) { final List commentModels = new ArrayList<>(); - String endCursor = ""; - while (endCursor != null) { + String childEndCursor = ""; + while (childEndCursor != null) { final String url = "https://www.instagram.com/graphql/query/?query_hash=51fdd02b67508306ad4484ff574a0b62&variables=" + - "{\"comment_id\":\"" + commentId + "\",\"first\":50,\"after\":\"" + endCursor + "\"}"; - + "{\"comment_id\":\"" + commentId + "\",\"first\":50,\"after\":\"" + childEndCursor + "\"}"; try { final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); conn.setUseCaches(false); @@ -93,8 +95,8 @@ public final class CommentsFetcher extends AsyncTask getParentComments() { final List commentModels = new ArrayList<>(); - String endCursor = ""; - while (endCursor != null) { final String url = "https://www.instagram.com/graphql/query/?query_hash=bc3296d1ce80a24b1b6e40b1e72903f5&variables=" + - "{\"shortcode\":\"" + shortCode + "\",\"first\":50,\"after\":\"" + endCursor + "\"}"; - - try { + "{\"shortcode\":\"" + shortCode + "\",\"first\":50,\"after\":\"" + endCursor.replace("\"", "\\\"") + "\"}"; + try { final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); conn.setUseCaches(false); conn.connect(); - if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) break; + if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) return null; else { final JSONObject parentComments = new JSONObject(NetworkUtils.readFromConnection(conn)).getJSONObject("data") .getJSONObject("shortcode_media") @@ -170,8 +169,7 @@ public final class CommentsFetcher extends AsyncTask("commentModelsList.size", commentModels.size())); if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); - break; + return null; } - } return commentModels; } } diff --git a/app/src/main/java/awais/instagrabber/asyncs/FeedStoriesFetcher.java b/app/src/main/java/awais/instagrabber/asyncs/FeedStoriesFetcher.java deleted file mode 100755 index c975dc5d..00000000 --- a/app/src/main/java/awais/instagrabber/asyncs/FeedStoriesFetcher.java +++ /dev/null @@ -1,93 +0,0 @@ -package awais.instagrabber.asyncs; - -import android.os.AsyncTask; -import android.util.Log; - -import org.json.JSONArray; -import org.json.JSONObject; - -import java.net.HttpURLConnection; -import java.net.URL; - -import awais.instagrabber.BuildConfig; -import awais.instagrabber.interfaces.FetchListener; -import awais.instagrabber.models.FeedStoryModel; -import awais.instagrabber.models.ProfileModel; -import awais.instagrabber.utils.Constants; -import awais.instagrabber.utils.NetworkUtils; -import awaisomereport.LogCollector.LogFile; - -import static awais.instagrabber.utils.Utils.logCollector; - -public final class FeedStoriesFetcher extends AsyncTask { - private final FetchListener fetchListener; - - public FeedStoriesFetcher(final FetchListener fetchListener) { - this.fetchListener = fetchListener; - } - - @Override - protected FeedStoryModel[] doInBackground(final Void... voids) { - FeedStoryModel[] result = null; - String url = "https://www.instagram.com/graphql/query/?query_hash=b7b84d884400bc5aa7cfe12ae843a091&variables=" + - "{\"only_stories\":true,\"stories_prefetch\":false,\"stories_video_dash_manifest\":false}"; - - try { - HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); - conn.setInstanceFollowRedirects(false); - conn.setUseCaches(false); - conn.connect(); - - if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { - final JSONArray feedStoriesReel = new JSONObject(NetworkUtils.readFromConnection(conn)) - .getJSONObject("data") - .getJSONObject(Constants.EXTRAS_USER) - .getJSONObject("feed_reels_tray") - .getJSONObject("edge_reels_tray_to_reel") - .getJSONArray("edges"); - - conn.disconnect(); - - final int storiesLen = feedStoriesReel.length(); - final FeedStoryModel[] feedStoryModels = new FeedStoryModel[storiesLen]; - final String[] feedStoryIDs = new String[storiesLen]; - - for (int i = 0; i < storiesLen; ++i) { - final JSONObject node = feedStoriesReel.getJSONObject(i).getJSONObject("node"); - - final JSONObject user = node.getJSONObject(node.has("user") ? "user" : "owner"); - final ProfileModel profileModel = new ProfileModel(false, false, false, - user.getString("id"), - user.getString("username"), - null, null, null, - user.getString("profile_pic_url"), - null, 0, 0, 0, false, false, false, false); - - final String id = node.getString("id"); - final boolean fullyRead = !node.isNull("seen") && node.getLong("seen") == node.getLong("latest_reel_media"); - feedStoryIDs[i] = id; - feedStoryModels[i] = new FeedStoryModel(id, profileModel, fullyRead); - } - result = feedStoryModels; - } - - conn.disconnect(); - } catch (final Exception e) { - if (logCollector != null) - logCollector.appendException(e, LogFile.ASYNC_FEED_STORY_FETCHER, "doInBackground"); - if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); - } - - return result; - } - - @Override - protected void onPreExecute() { - if (fetchListener != null) fetchListener.doBefore(); - } - - @Override - protected void onPostExecute(final FeedStoryModel[] result) { - if (fetchListener != null) fetchListener.onResult(result); - } -} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/fragments/CommentsViewerFragment.java b/app/src/main/java/awais/instagrabber/fragments/CommentsViewerFragment.java index c716c989..f39b2ea1 100644 --- a/app/src/main/java/awais/instagrabber/fragments/CommentsViewerFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/CommentsViewerFragment.java @@ -32,11 +32,18 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import com.google.android.material.bottomsheet.BottomSheetDialogFragment; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +import awais.instagrabber.BuildConfig; import awais.instagrabber.R; import awais.instagrabber.adapters.CommentsAdapter; import awais.instagrabber.asyncs.CommentsFetcher; +import awais.instagrabber.customviews.helpers.RecyclerLazyLoader; import awais.instagrabber.databinding.FragmentCommentsBinding; import awais.instagrabber.dialogs.ProfilePicDialogFragment; +import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.models.CommentModel; import awais.instagrabber.models.ProfileModel; import awais.instagrabber.utils.Constants; @@ -56,8 +63,9 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl private CommentsAdapter commentsAdapter; private FragmentCommentsBinding binding; - private String shortCode; - private String userId; + private LinearLayoutManager layoutManager; + private RecyclerLazyLoader lazyLoader; + private String shortCode, userId, endCursor = null; private Resources resources; private InputMethodManager imm; private AppCompatActivity fragmentActivity; @@ -65,8 +73,30 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl private boolean shouldRefresh = true; private MediaService mediaService; private String postId; + private AsyncTask> currentlyRunning; private CommentsViewModel commentsViewModel; + private final FetchListener> fetchListener = new FetchListener>() { + @Override + public void doBefore() { + binding.swipeRefreshLayout.setRefreshing(true); + } + + @Override + public void onResult(final List commentModels) { + endCursor = commentModels.get(0).getEndCursor(); + if (commentModels != null && commentModels.size() > 0) { + List list = commentsViewModel.getList().getValue(); + list = list != null ? new LinkedList<>(list) : new LinkedList<>(); + // final int oldSize = list != null ? list.size() : 0; + list.addAll(commentModels); + commentsViewModel.getList().postValue(list); + } + binding.swipeRefreshLayout.setRefreshing(false); + stopCurrentExecutor(); + } + }; + private final CommentsAdapter.CommentCallback commentCallback = new CommentsAdapter.CommentCallback() { @Override public void onClick(final CommentModel comment) { @@ -181,11 +211,11 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl @Override public void onRefresh() { - binding.swipeRefreshLayout.setRefreshing(true); - new CommentsFetcher(shortCode, commentModels -> { - commentsViewModel.getList().postValue(commentModels); - binding.swipeRefreshLayout.setRefreshing(false); - }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + endCursor = null; + lazyLoader.resetState(); + commentsViewModel.getList().postValue(Collections.emptyList()); + stopCurrentExecutor(); + currentlyRunning = new CommentsFetcher(shortCode, "", fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } private void init() { @@ -198,7 +228,8 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl binding.swipeRefreshLayout.setOnRefreshListener(this); binding.swipeRefreshLayout.setRefreshing(true); commentsViewModel = new ViewModelProvider(this).get(CommentsViewModel.class); - binding.rvComments.setLayoutManager(new LinearLayoutManager(getContext())); + layoutManager = new LinearLayoutManager(getContext()); + binding.rvComments.setLayoutManager(layoutManager); commentsAdapter = new CommentsAdapter(commentCallback); binding.rvComments.setAdapter(commentsAdapter); commentsViewModel.getList().observe(getViewLifecycleOwner(), commentsAdapter::submitList); @@ -226,6 +257,13 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl }); binding.commentField.setEndIconOnClickListener(newCommentListener); } + lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> { + if (!TextUtils.isEmpty(endCursor)) + currentlyRunning = new CommentsFetcher(shortCode, endCursor, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + endCursor = null; + }); + binding.rvComments.addOnScrollListener(lazyLoader); + stopCurrentExecutor(); onRefresh(); } @@ -301,8 +339,6 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl Utils.copyText(context, "@" + profileModel.getUsername() + ": " + commentModel.getText()); break; case 3: // reply to comment - // final View focus = binding.rvComments.findViewWithTag(commentModel); - // focus.setBackgroundColor(0x80888888); commentsAdapter.setSelected(commentModel); String mention = "@" + profileModel.getUsername() + " "; binding.commentText.setText(mention); @@ -326,7 +362,7 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); return; } - onRefresh(); + commentsAdapter.setLiked(commentModel, true); } @Override @@ -344,7 +380,7 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); return; } - onRefresh(); + commentsAdapter.setLiked(commentModel, false); } @Override @@ -389,4 +425,14 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl final NavDirections action = CommentsViewerFragmentDirections.actionGlobalProfileFragment(username); NavHostFragment.findNavController(this).navigate(action); } + + private void stopCurrentExecutor() { + if (currentlyRunning != null) { + try { + currentlyRunning.cancel(true); + } catch (final Exception e) { + if (BuildConfig.DEBUG) Log.e(TAG, "", e); + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/models/CommentModel.java b/app/src/main/java/awais/instagrabber/models/CommentModel.java index 9ce02bb0..c66f6916 100755 --- a/app/src/main/java/awais/instagrabber/models/CommentModel.java +++ b/app/src/main/java/awais/instagrabber/models/CommentModel.java @@ -11,11 +11,10 @@ public class CommentModel { private final ProfileModel profileModel; private final String id; private final String text; - private final long likes; + private long likes; private final long timestamp; private List childCommentModels; - private final boolean liked; - private boolean hasNextPage; + private boolean liked, hasNextPage; private String endCursor; public CommentModel(final String id, @@ -53,6 +52,11 @@ public class CommentModel { return liked; } + public void setLiked(boolean liked) { + this.likes = liked ? likes + 1 : likes - 1; + this.liked = liked; + } + public ProfileModel getProfileModel() { return profileModel; } diff --git a/app/src/main/res/values/color.xml b/app/src/main/res/values/color.xml index c59baf9a..b46d1762 100755 --- a/app/src/main/res/values/color.xml +++ b/app/src/main/res/values/color.xml @@ -31,6 +31,7 @@ #efefef #888888 + #40FF69B4 #FFFFFF #80FFFFFF