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)
This commit is contained in:
Austin Huang 2020-11-15 19:51:00 -05:00
parent 519d46858b
commit bf24f56843
No known key found for this signature in database
GPG Key ID: 84C23AA04587A91F
8 changed files with 276 additions and 296 deletions

View File

@ -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<FeedModel, FeedItemViewHolder> {
// 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<FeedModel> diffCallback = new DiffUtil.ItemCallback<FeedModel>() {
// @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();
// }
// }

View File

@ -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<Void, Void, FollowModel[]> {
private final String endCursor, id;
private final boolean isFollowers;
private final FetchListener<FollowModel[]> fetchListener;
public FollowFetcher(final String id, final boolean isFollowers, final FetchListener<FollowModel[]> 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<FollowModel[]> 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);
}
}

View File

@ -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<FollowModel> followersModels = new ArrayList<>();
private final ArrayList<FollowModel> 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<Void, Void, FollowModel[]> 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<FollowModel[]> fetchListener = new FetchListener<FollowModel[]>() {
final ServiceCallback<FriendshipRepoListFetchResponse> cb = new ServiceCallback<FriendshipRepoListFetchResponse>() {
@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<FollowModel[]> followingFetchListener = new FetchListener<FollowModel[]>() {
final ServiceCallback<FriendshipRepoListFetchResponse> followingFetchCb = new ServiceCallback<FriendshipRepoListFetchResponse>() {
@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<FollowModel[]> followersFetchListener = new FetchListener<FollowModel[]>() {
@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<FriendshipRepoListFetchResponse> followersFetchCb = new ServiceCallback<FriendshipRepoListFetchResponse>() {
@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<FollowModel> followingModels,
final ArrayList<FollowModel> followersModels,
final ArrayList<FollowModel> allFollowing) {
loading = false;
final ArrayList<ExpandableGroup> 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);
}
}
}
}
}

View File

@ -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);

View File

@ -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<FriendshipRepoRestrictRootResponse> toggleRestrict(@Header("User-Agent") String userAgent,
@Path("action") String action,
@FieldMap Map<String, String> form);
@GET("/api/v1/friendships/{userId}/{type}/")
Call<String> getList(@Header("User-Agent") String userAgent,
@Path("userId") String userId,
@Path("type") String type, // following or followers
@QueryMap(encoded = true) Map<String, String> queryParams);
}

View File

@ -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<FollowModel> items;
public FriendshipRepoListFetchResponse(final String nextMaxId,
final String status,
final List<FollowModel> 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<FollowModel> getItems() {
return items;
}
public FriendshipRepoListFetchResponse setItems(final List<FollowModel> 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 +
'}';
}
}

View File

@ -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<FriendshipRepoListFetchResponse> callback) {
final Map<String, String> queryMap = new HashMap<>();
queryMap.put("max_id", maxId == null ? "" : maxId);
final Call<String> request = repository.getList(Constants.I_USER_AGENT,
targetUserId,
follower ? "followers" : "following",
queryMap);
request.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> 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<String> 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<FollowModel> items = parseItems(itemsJson);
return new FriendshipRepoListFetchResponse(
nextMaxId,
status,
items
);
}
private List<FollowModel> parseItems(final JSONArray items) throws JSONException {
if (items == null) {
return Collections.emptyList();
}
final List<FollowModel> 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;
}
}

View File

@ -328,6 +328,7 @@
<string name="corners">Corners</string>
<string name="show_grid_gap">Show grid gap</string>
<string name="disable_animation">Disable animation</string>
<string name="follower_wait_to_load">Please wait for the current task to complete first!</string>
<plurals name="likes_count">
<item quantity="one">%d like</item>
<item quantity="other">%d likes</item>