From bf24f56843009dfed28354a3a703848c730158cf Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Sun, 15 Nov 2020 19:51:00 -0500 Subject: [PATCH] redo follower/ing viewer Follower/ing viewer now uses the i endpoint (which returns more users at once) as well as caching (less requests needed) so its response time has decreased significantly (to 1/3 in my case) --- .../instagrabber/adapters/FeedAdapter.java | 111 ----------- .../instagrabber/asyncs/FollowFetcher.java | 101 ---------- .../fragments/FollowViewerFragment.java | 181 ++++++++++-------- .../fragments/main/ProfileFragment.java | 2 +- .../repositories/FriendshipRepository.java | 9 + .../FriendshipRepoListFetchResponse.java | 79 ++++++++ .../webservices/FriendshipService.java | 88 +++++++++ app/src/main/res/values/strings.xml | 1 + 8 files changed, 276 insertions(+), 296 deletions(-) delete mode 100755 app/src/main/java/awais/instagrabber/adapters/FeedAdapter.java delete mode 100755 app/src/main/java/awais/instagrabber/asyncs/FollowFetcher.java create mode 100644 app/src/main/java/awais/instagrabber/repositories/responses/FriendshipRepoListFetchResponse.java diff --git a/app/src/main/java/awais/instagrabber/adapters/FeedAdapter.java b/app/src/main/java/awais/instagrabber/adapters/FeedAdapter.java deleted file mode 100755 index 6ffc88e0..00000000 --- a/app/src/main/java/awais/instagrabber/adapters/FeedAdapter.java +++ /dev/null @@ -1,111 +0,0 @@ -// package awais.instagrabber.adapters; -// -// import android.content.Context; -// import android.view.LayoutInflater; -// import android.view.View; -// import android.view.ViewGroup; -// -// import androidx.annotation.NonNull; -// import androidx.recyclerview.widget.DiffUtil; -// import androidx.recyclerview.widget.ListAdapter; -// -// import awais.instagrabber.adapters.viewholder.feed.FeedItemViewHolder; -// import awais.instagrabber.adapters.viewholder.feed.FeedPhotoViewHolder; -// import awais.instagrabber.adapters.viewholder.feed.FeedSliderViewHolder; -// import awais.instagrabber.adapters.viewholder.feed.FeedVideoViewHolder; -// import awais.instagrabber.customviews.RamboTextView; -// import awais.instagrabber.databinding.ItemFeedPhotoBinding; -// import awais.instagrabber.databinding.ItemFeedSliderBinding; -// import awais.instagrabber.databinding.ItemFeedVideoBinding; -// import awais.instagrabber.interfaces.MentionClickListener; -// import awais.instagrabber.models.FeedModel; -// import awais.instagrabber.models.enums.MediaItemType; -// import awais.instagrabber.utils.Utils; -// -// public final class FeedAdapter extends ListAdapter { -// private static final String TAG = "FeedAdapter"; -// private final View.OnClickListener clickListener; -// private final MentionClickListener mentionClickListener; -// private final View.OnLongClickListener longClickListener = v -> { -// final Object tag; -// if (v instanceof RamboTextView && (tag = v.getTag()) instanceof FeedModel) -// Utils.copyText(v.getContext(), ((FeedModel) tag).getPostCaption()); -// return true; -// }; -// -// private static final DiffUtil.ItemCallback diffCallback = new DiffUtil.ItemCallback() { -// @Override -// public boolean areItemsTheSame(@NonNull final FeedModel oldItem, @NonNull final FeedModel newItem) { -// return oldItem.getPostId().equals(newItem.getPostId()); -// } -// -// @Override -// public boolean areContentsTheSame(@NonNull final FeedModel oldItem, @NonNull final FeedModel newItem) { -// return oldItem.getPostId().equals(newItem.getPostId()); -// } -// }; -// -// public FeedAdapter(final View.OnClickListener clickListener, -// final MentionClickListener mentionClickListener) { -// super(diffCallback); -// // private final static String ellipsize = "… more"; -// this.clickListener = clickListener; -// this.mentionClickListener = mentionClickListener; -// } -// -// @NonNull -// @Override -// public FeedItemViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { -// final Context context = parent.getContext(); -// final LayoutInflater layoutInflater = LayoutInflater.from(context); -// final MediaItemType type = MediaItemType.valueOf(viewType); -// switch (type) { -// case MEDIA_TYPE_VIDEO: { -// final ItemFeedVideoBinding binding = ItemFeedVideoBinding.inflate(layoutInflater, parent, false); -// return new FeedVideoViewHolder(binding, mentionClickListener, clickListener, longClickListener); -// } -// case MEDIA_TYPE_SLIDER: { -// final ItemFeedSliderBinding binding = ItemFeedSliderBinding.inflate(layoutInflater, parent, false); -// return new FeedSliderViewHolder(binding, mentionClickListener, clickListener, longClickListener); -// } -// case MEDIA_TYPE_IMAGE: -// default: { -// final ItemFeedPhotoBinding binding = ItemFeedPhotoBinding.inflate(layoutInflater, parent, false); -// return new FeedPhotoViewHolder(binding, mentionClickListener, clickListener, longClickListener); -// } -// } -// } -// -// @Override -// public void onBindViewHolder(@NonNull final FeedItemViewHolder viewHolder, final int position) { -// final FeedModel feedModel = getItem(position); -// if (feedModel == null) { -// return; -// } -// feedModel.setPosition(position); -// viewHolder.bind(feedModel, (feedModel1, view, postImage) -> {}); -// } -// -// @Override -// public int getItemViewType(final int position) { -// return getItem(position).getItemType().getId(); -// } -// -// @Override -// public void onViewAttachedToWindow(@NonNull final FeedItemViewHolder holder) { -// super.onViewAttachedToWindow(holder); -// // Log.d(TAG, "attached holder: " + holder); -// if (!(holder instanceof FeedSliderViewHolder)) return; -// final FeedSliderViewHolder feedSliderViewHolder = (FeedSliderViewHolder) holder; -// feedSliderViewHolder.startPlayingVideo(); -// } -// -// @Override -// public void onViewDetachedFromWindow(@NonNull final FeedItemViewHolder holder) { -// super.onViewDetachedFromWindow(holder); -// // Log.d(TAG, "detached holder: " + holder); -// if (!(holder instanceof FeedSliderViewHolder)) return; -// final FeedSliderViewHolder feedSliderViewHolder = (FeedSliderViewHolder) holder; -// feedSliderViewHolder.stopPlayingVideo(); -// } -// } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/asyncs/FollowFetcher.java b/app/src/main/java/awais/instagrabber/asyncs/FollowFetcher.java deleted file mode 100755 index f0cb7aa1..00000000 --- a/app/src/main/java/awais/instagrabber/asyncs/FollowFetcher.java +++ /dev/null @@ -1,101 +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.FollowModel; -import awais.instagrabber.utils.Constants; -import awais.instagrabber.utils.NetworkUtils; -import awaisomereport.LogCollector; - -import static awais.instagrabber.utils.Utils.logCollector; - -public final class FollowFetcher extends AsyncTask { - private final String endCursor, id; - private final boolean isFollowers; - private final FetchListener fetchListener; - - public FollowFetcher(final String id, final boolean isFollowers, final FetchListener fetchListener) { - this.id = id; - this.endCursor = ""; - this.isFollowers = isFollowers; - this.fetchListener = fetchListener; - } - - public FollowFetcher(final String id, final boolean isFollowers, final String endCursor, final FetchListener fetchListener) { - this.id = id; - this.endCursor = endCursor == null ? "" : endCursor; - this.isFollowers = isFollowers; - this.fetchListener = fetchListener; - } - - @Override - protected void onPreExecute() { - if (fetchListener != null) fetchListener.doBefore(); - } - - @Override - protected FollowModel[] doInBackground(final Void... voids) { - FollowModel[] result = null; - final String url = "https://www.instagram.com/graphql/query/?query_id=" + (isFollowers ? "17851374694183129" : "17874545323001329") - + "&id=" + id + "&first=50&after=" + endCursor; - - try { - final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); - conn.setInstanceFollowRedirects(false); - conn.setUseCaches(false); - conn.connect(); - - if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { - final JSONObject data = new JSONObject(NetworkUtils.readFromConnection(conn)).getJSONObject("data") - .getJSONObject(Constants.EXTRAS_USER).getJSONObject(isFollowers ? "edge_followed_by" : "edge_follow"); - - final String endCursor; - final boolean hasNextPage; - - final JSONObject pageInfo = data.getJSONObject("page_info"); - if (pageInfo.has("has_next_page")) { - hasNextPage = pageInfo.getBoolean("has_next_page"); - endCursor = hasNextPage ? pageInfo.getString("end_cursor") : null; - } else { - hasNextPage = false; - endCursor = null; - } - - final JSONArray edges = data.getJSONArray("edges"); - final FollowModel[] models = new FollowModel[edges.length()]; - for (int i = 0; i < models.length; ++i) { - final JSONObject followNode = edges.getJSONObject(i).getJSONObject("node"); - models[i] = new FollowModel(followNode.getString(Constants.EXTRAS_ID), followNode.getString(Constants.EXTRAS_USERNAME), - followNode.getString("full_name"), followNode.getString("profile_pic_url")); - } - - if (models[models.length - 1] != null) - models[models.length - 1].setPageCursor(hasNextPage, endCursor); - - result = models; - } - - conn.disconnect(); - } catch (final Exception e) { - if (logCollector != null) - logCollector.appendException(e, LogCollector.LogFile.ASYNC_FOLLOW_FETCHER, "doInBackground"); - if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); - } - - return result; - } - - @Override - protected void onPostExecute(final FollowModel[] result) { - if (fetchListener != null) fetchListener.onResult(result); - } -} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/fragments/FollowViewerFragment.java b/app/src/main/java/awais/instagrabber/fragments/FollowViewerFragment.java index 4747fdda..f27d2c91 100644 --- a/app/src/main/java/awais/instagrabber/fragments/FollowViewerFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/FollowViewerFragment.java @@ -1,5 +1,6 @@ package awais.instagrabber.fragments; +import android.content.Context; import android.content.res.Resources; import android.os.AsyncTask; import android.os.Bundle; @@ -10,6 +11,7 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -26,15 +28,20 @@ import java.util.Arrays; import awais.instagrabber.BuildConfig; import awais.instagrabber.R; import awais.instagrabber.adapters.FollowAdapter; -import awais.instagrabber.asyncs.FollowFetcher; import awais.instagrabber.databinding.FragmentFollowersViewerBinding; -import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.models.FollowModel; +import awais.instagrabber.repositories.responses.FriendshipRepoChangeRootResponse; +import awais.instagrabber.repositories.responses.FriendshipRepoListFetchResponse; +import awais.instagrabber.utils.Constants; +import awais.instagrabber.utils.CookieUtils; import awais.instagrabber.utils.TextUtils; +import awais.instagrabber.webservices.FriendshipService; +import awais.instagrabber.webservices.ServiceCallback; import awaisomereport.LogCollector; import thoughtbot.expandableadapter.ExpandableGroup; import static awais.instagrabber.utils.Utils.logCollector; +import static awais.instagrabber.utils.Utils.settingsHelper; public final class FollowViewerFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { private static final String TAG = "FollowViewerFragment"; @@ -44,7 +51,7 @@ public final class FollowViewerFragment extends Fragment implements SwipeRefresh private final ArrayList followersModels = new ArrayList<>(); private final ArrayList allFollowing = new ArrayList<>(); - private boolean isFollowersList, isCompare = false; + private boolean isFollowersList, isCompare = false, loading = false; private String profileId, username, namePost, type; private Resources resources; private FollowModel model; @@ -53,12 +60,14 @@ public final class FollowViewerFragment extends Fragment implements SwipeRefresh private FragmentFollowersViewerBinding binding; private AsyncTask currentlyExecuting; private SwipeRefreshLayout root; + private FriendshipService friendshipService; private boolean shouldRefresh = true; private AppCompatActivity fragmentActivity; @Override public void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); + friendshipService = FriendshipService.getInstance(); fragmentActivity = (AppCompatActivity) getActivity(); setHasOptionsMenu(true); } @@ -135,102 +144,114 @@ public final class FollowViewerFragment extends Fragment implements SwipeRefresh } private void listFollows() { - stopCurrentExecutor(); + loading = true; type = resources.getString(isFollowersList ? R.string.followers_type_followers : R.string.followers_type_following); setSubtitle(type); followModels.clear(); - final FetchListener fetchListener = new FetchListener() { + final ServiceCallback cb = new ServiceCallback() { @Override - public void doBefore() { - binding.swipeRefreshLayout.setRefreshing(true); - } - - @Override - public void onResult(final FollowModel[] result) { - if (result == null) binding.swipeRefreshLayout.setRefreshing(false); + public void onSuccess(final FriendshipRepoListFetchResponse result) { + if (result == null) { + binding.swipeRefreshLayout.setRefreshing(false); + return; + } else { - followModels.addAll(Arrays.asList(result)); - - final FollowModel model = result[result.length - 1]; - if (model != null && model.hasNextPage()) { - stopCurrentExecutor(); - currentlyExecuting = new FollowFetcher(profileId, isFollowersList, model.getEndCursor(), this) - .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - model.setPageCursor(false, null); - } else { + followModels.addAll(result.getItems()); + if (result.isMoreAvailable()) { + friendshipService.getList(isFollowersList, profileId, result.getNextMaxId(), this); + } + else { binding.swipeRefreshLayout.setRefreshing(false); - + if (isFollowersList) followersModels.addAll(followModels); + else followingModels.addAll(followModels); refreshAdapter(followModels, null, null, null); } } } + + @Override + public void onFailure(final Throwable t) { + binding.swipeRefreshLayout.setRefreshing(false); + Log.e(TAG, "Error fetching list (single)", t); + } }; - currentlyExecuting = new FollowFetcher(profileId, isFollowersList, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + binding.swipeRefreshLayout.setRefreshing(true); + friendshipService.getList(isFollowersList, profileId, null, cb); } private void listCompare() { - stopCurrentExecutor(); + loading = true; setSubtitle(R.string.followers_compare); allFollowing.clear(); followersModels.clear(); followingModels.clear(); - final FetchListener followingFetchListener = new FetchListener() { + final ServiceCallback followingFetchCb = new ServiceCallback() { @Override - public void onResult(final FollowModel[] result) { + public void onSuccess(final FriendshipRepoListFetchResponse result) { if (result != null) { - followingModels.addAll(Arrays.asList(result)); + followingModels.addAll(result.getItems()); - final FollowModel model = result[result.length - 1]; - if (model != null && model.hasNextPage()) { - stopCurrentExecutor(); - currentlyExecuting = new FollowFetcher(profileId, false, model.getEndCursor(), this) - .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - model.setPageCursor(false, null); + if (result.isMoreAvailable()) { + friendshipService.getList(false, profileId, result.getNextMaxId(), this); } else { - allFollowing.addAll(followersModels); - allFollowing.retainAll(followingModels); - - for (final FollowModel followModel : allFollowing) { - followersModels.remove(followModel); - followingModels.remove(followModel); - } - - allFollowing.trimToSize(); - followersModels.trimToSize(); - followingModels.trimToSize(); - - binding.swipeRefreshLayout.setRefreshing(false); - - refreshAdapter(null, followingModels, followersModels, allFollowing); + showCompare(); } } else binding.swipeRefreshLayout.setRefreshing(false); } - }; - final FetchListener followersFetchListener = new FetchListener() { - @Override - public void doBefore() { - binding.swipeRefreshLayout.setRefreshing(true); - } @Override - public void onResult(final FollowModel[] result) { + public void onFailure(final Throwable t) { + binding.swipeRefreshLayout.setRefreshing(false); + Log.e(TAG, "Error fetching list (double, following)", t); + } + }; + final ServiceCallback followersFetchCb = new ServiceCallback() { + @Override + public void onSuccess(final FriendshipRepoListFetchResponse result) { if (result != null) { - followersModels.addAll(Arrays.asList(result)); - final FollowModel model = result[result.length - 1]; - if (model == null || !model.hasNextPage()) { - stopCurrentExecutor(); - currentlyExecuting = new FollowFetcher(profileId, false, followingFetchListener) - .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + followersModels.addAll(result.getItems()); + if (result.isMoreAvailable()) { + friendshipService.getList(true, profileId, result.getNextMaxId(), this); + } else if (followingModels.size() == 0) { + friendshipService.getList(false, profileId, null, followingFetchCb); } else { - stopCurrentExecutor(); - currentlyExecuting = new FollowFetcher(profileId, true, model.getEndCursor(), this) - .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - model.setPageCursor(false, null); + showCompare(); } } } + + @Override + public void onFailure(final Throwable t) { + binding.swipeRefreshLayout.setRefreshing(false); + Log.e(TAG, "Error fetching list (double, follower)", t); + } }; - currentlyExecuting = new FollowFetcher(profileId, true, followersFetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + binding.swipeRefreshLayout.setRefreshing(true); + if (followersModels.size() == 0) { + friendshipService.getList(true, profileId, null, followersFetchCb); + } + else if (followingModels.size() == 0) { + friendshipService.getList(false, profileId, null, followingFetchCb); + } + else showCompare(); + } + + private void showCompare() { + allFollowing.addAll(followersModels); + allFollowing.retainAll(followingModels); + + for (final FollowModel followModel : allFollowing) { + followersModels.remove(followModel); + followingModels.remove(followModel); + } + + allFollowing.trimToSize(); + followersModels.trimToSize(); + followingModels.trimToSize(); + + binding.swipeRefreshLayout.setRefreshing(false); + + refreshAdapter(null, followingModels, followersModels, allFollowing); } @Override @@ -322,9 +343,16 @@ public final class FollowViewerFragment extends Fragment implements SwipeRefresh public boolean onOptionsItemSelected(@NonNull final MenuItem item) { if (item.getItemId() != R.id.action_compare) return super.onOptionsItemSelected(item); binding.rvFollow.setAdapter(null); - if (isCompare) listFollows(); - else listCompare(); - isCompare = !isCompare; + final Context context = getContext(); + if (loading) Toast.makeText(context, R.string.follower_wait_to_load, Toast.LENGTH_LONG).show(); + else if (isCompare) { + listFollows(); + isCompare = !isCompare; + } + else { + listCompare(); + isCompare = !isCompare; + } return true; } @@ -332,6 +360,7 @@ public final class FollowViewerFragment extends Fragment implements SwipeRefresh final ArrayList followingModels, final ArrayList followersModels, final ArrayList allFollowing) { + loading = false; final ArrayList groups = new ArrayList<>(1); if (isCompare) { @@ -349,18 +378,4 @@ public final class FollowViewerFragment extends Fragment implements SwipeRefresh adapter.toggleGroup(0); binding.rvFollow.setAdapter(adapter); } - - public void stopCurrentExecutor() { - if (currentlyExecuting != null) { - try { - currentlyExecuting.cancel(true); - } catch (final Exception e) { - if (logCollector != null) - logCollector.appendException(e, LogCollector.LogFile.MAIN_HELPER, "stopCurrentExecutor"); - if (BuildConfig.DEBUG) { - Log.e(TAG, "", e); - } - } - } - } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java index 41bca0cc..afc05d6a 100644 --- a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java @@ -612,7 +612,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe return true; }); } - if (!profileModel.isReallyPrivate()) { + if (!profileModel.isReallyPrivate() && isLoggedIn) { binding.mainFollowing.setClickable(true); binding.mainFollowers.setClickable(true); diff --git a/app/src/main/java/awais/instagrabber/repositories/FriendshipRepository.java b/app/src/main/java/awais/instagrabber/repositories/FriendshipRepository.java index 699874d7..d01aa1ee 100644 --- a/app/src/main/java/awais/instagrabber/repositories/FriendshipRepository.java +++ b/app/src/main/java/awais/instagrabber/repositories/FriendshipRepository.java @@ -3,13 +3,16 @@ package awais.instagrabber.repositories; import java.util.Map; import awais.instagrabber.repositories.responses.FriendshipRepoChangeRootResponse; +import awais.instagrabber.repositories.responses.FriendshipRepoListFetchResponse; import awais.instagrabber.repositories.responses.FriendshipRepoRestrictRootResponse; import retrofit2.Call; import retrofit2.http.FieldMap; import retrofit2.http.FormUrlEncoded; +import retrofit2.http.GET; import retrofit2.http.Header; import retrofit2.http.POST; import retrofit2.http.Path; +import retrofit2.http.QueryMap; public interface FriendshipRepository { @@ -25,4 +28,10 @@ public interface FriendshipRepository { Call toggleRestrict(@Header("User-Agent") String userAgent, @Path("action") String action, @FieldMap Map form); + + @GET("/api/v1/friendships/{userId}/{type}/") + Call getList(@Header("User-Agent") String userAgent, + @Path("userId") String userId, + @Path("type") String type, // following or followers + @QueryMap(encoded = true) Map queryParams); } diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/FriendshipRepoListFetchResponse.java b/app/src/main/java/awais/instagrabber/repositories/responses/FriendshipRepoListFetchResponse.java new file mode 100644 index 00000000..843db036 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/repositories/responses/FriendshipRepoListFetchResponse.java @@ -0,0 +1,79 @@ +package awais.instagrabber.repositories.responses; + +import androidx.annotation.NonNull; + +import java.util.List; +import java.util.Objects; + +import awais.instagrabber.models.FollowModel; +import awais.instagrabber.utils.TextUtils; + +public class FriendshipRepoListFetchResponse { + private String nextMaxId; + private String status; + private List items; + + public FriendshipRepoListFetchResponse(final String nextMaxId, + final String status, + final List items) { + this.nextMaxId = nextMaxId; + this.status = status; + this.items = items; + } + + public boolean isMoreAvailable() { + return !TextUtils.isEmpty(nextMaxId); + } + + public String getNextMaxId() { + return nextMaxId; + } + + public FriendshipRepoListFetchResponse setNextMaxId(final String nextMaxId) { + this.nextMaxId = nextMaxId; + return this; + } + + public String getStatus() { + return status; + } + + public FriendshipRepoListFetchResponse setStatus(final String status) { + this.status = status; + return this; + } + + public List getItems() { + return items; + } + + public FriendshipRepoListFetchResponse setItems(final List items) { + this.items = items; + return this; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final FriendshipRepoListFetchResponse that = (FriendshipRepoListFetchResponse) o; + return Objects.equals(nextMaxId, that.nextMaxId) && + Objects.equals(status, that.status) && + Objects.equals(items, that.items); + } + + @Override + public int hashCode() { + return Objects.hash(nextMaxId, status, items); + } + + @NonNull + @Override + public String toString() { + return "FriendshipRepoListFetchResponse{" + + "nextMaxId='" + nextMaxId + '\'' + + ", status='" + status + '\'' + + ", items=" + items + + '}'; + } +} diff --git a/app/src/main/java/awais/instagrabber/webservices/FriendshipService.java b/app/src/main/java/awais/instagrabber/webservices/FriendshipService.java index 9874a855..a370b7df 100644 --- a/app/src/main/java/awais/instagrabber/webservices/FriendshipService.java +++ b/app/src/main/java/awais/instagrabber/webservices/FriendshipService.java @@ -1,15 +1,28 @@ package awais.instagrabber.webservices; +import android.util.Log; + import androidx.annotation.NonNull; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.UUID; +import awais.instagrabber.models.FollowModel; import awais.instagrabber.repositories.FriendshipRepository; import awais.instagrabber.repositories.responses.FriendshipRepoChangeRootResponse; +import awais.instagrabber.repositories.responses.FriendshipRepoListFetchResponse; import awais.instagrabber.repositories.responses.FriendshipRepoRestrictRootResponse; import awais.instagrabber.utils.Constants; +import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.Utils; import retrofit2.Call; import retrofit2.Callback; @@ -139,4 +152,79 @@ public class FriendshipService extends BaseService { } }); } + + // log in required + public void getList(final boolean follower, + final String targetUserId, + final String maxId, + final ServiceCallback callback) { + final Map queryMap = new HashMap<>(); + queryMap.put("max_id", maxId == null ? "" : maxId); + final Call request = repository.getList(Constants.I_USER_AGENT, + targetUserId, + follower ? "followers" : "following", + queryMap); + request.enqueue(new Callback() { + @Override + public void onResponse(@NonNull final Call call, @NonNull final Response response) { + try { + if (callback == null) { + return; + } + final String body = response.body(); + if (TextUtils.isEmpty(body)) { + + callback.onSuccess(null); + return; + } + final FriendshipRepoListFetchResponse friendshipListFetchResponse = parseListResponse(body); + callback.onSuccess(friendshipListFetchResponse); + } catch (JSONException e) { + Log.e(TAG, "onResponse", e); + callback.onFailure(e); + } + } + + @Override + public void onFailure(@NonNull final Call call, @NonNull final Throwable t) { + if (callback != null) { + callback.onFailure(t); + } + } + }); + } + + private FriendshipRepoListFetchResponse parseListResponse(@NonNull final String body) throws JSONException { + final JSONObject root = new JSONObject(body); + final String nextMaxId = root.optString("next_max_id"); + final String status = root.optString("status"); + final JSONArray itemsJson = root.optJSONArray("users"); + final List items = parseItems(itemsJson); + return new FriendshipRepoListFetchResponse( + nextMaxId, + status, + items + ); + } + + private List parseItems(final JSONArray items) throws JSONException { + if (items == null) { + return Collections.emptyList(); + } + final List followModels = new ArrayList<>(); + for (int i = 0; i < items.length(); i++) { + final JSONObject itemJson = items.optJSONObject(i); + if (itemJson == null) { + continue; + } + final FollowModel followModel = new FollowModel(itemJson.getString("pk"), + itemJson.getString("username"), + itemJson.optString("full_name"), + itemJson.getString("profile_pic_url")); + if (followModel != null) { + followModels.add(followModel); + } + } + return followModels; + } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4fb90b66..69946394 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -328,6 +328,7 @@ Corners Show grid gap Disable animation + Please wait for the current task to complete first! %d like %d likes