mirror of
https://github.com/KokaKiwi/BarInsta
synced 2024-11-22 06:37:30 +00:00
greatly fix all the follower/ing stuff
closes #1334 (assuming) closes #1340 (by implementing API search) closes #1342 closes #1472 closes #1473
This commit is contained in:
parent
e4612a0dd0
commit
036edbea10
@ -12,12 +12,13 @@ import androidx.recyclerview.widget.RecyclerView;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import awais.instagrabber.R;
|
import awais.instagrabber.R;
|
||||||
import awais.instagrabber.adapters.viewholder.FollowsViewHolder;
|
import awais.instagrabber.adapters.viewholder.FollowsViewHolder;
|
||||||
import awais.instagrabber.databinding.ItemFollowBinding;
|
import awais.instagrabber.databinding.ItemFollowBinding;
|
||||||
import awais.instagrabber.interfaces.OnGroupClickListener;
|
import awais.instagrabber.interfaces.OnGroupClickListener;
|
||||||
import awais.instagrabber.models.FollowModel;
|
import awais.instagrabber.repositories.responses.User;
|
||||||
import awais.instagrabber.utils.TextUtils;
|
import awais.instagrabber.utils.TextUtils;
|
||||||
import thoughtbot.expandableadapter.ExpandableGroup;
|
import thoughtbot.expandableadapter.ExpandableGroup;
|
||||||
import thoughtbot.expandableadapter.ExpandableList;
|
import thoughtbot.expandableadapter.ExpandableList;
|
||||||
@ -27,28 +28,33 @@ import thoughtbot.expandableadapter.GroupViewHolder;
|
|||||||
// thanks to ThoughtBot's ExpandableRecyclerViewAdapter
|
// thanks to ThoughtBot's ExpandableRecyclerViewAdapter
|
||||||
// https://github.com/thoughtbot/expandable-recycler-view
|
// https://github.com/thoughtbot/expandable-recycler-view
|
||||||
public final class FollowAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements OnGroupClickListener, Filterable {
|
public final class FollowAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements OnGroupClickListener, Filterable {
|
||||||
|
private final View.OnClickListener onClickListener;
|
||||||
|
private final ExpandableList expandableListOriginal;
|
||||||
|
private final boolean hasManyGroups;
|
||||||
|
private ExpandableList expandableList;
|
||||||
|
|
||||||
private final Filter filter = new Filter() {
|
private final Filter filter = new Filter() {
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
protected FilterResults performFiltering(final CharSequence filter) {
|
protected FilterResults performFiltering(final CharSequence filter) {
|
||||||
if (expandableList.groups != null) {
|
final List<User> filteredItems = new ArrayList<User>();
|
||||||
final boolean isFilterEmpty = TextUtils.isEmpty(filter);
|
if (expandableListOriginal.groups == null || TextUtils.isEmpty(filter)) return null;
|
||||||
final String query = isFilterEmpty ? null : filter.toString().toLowerCase();
|
final String query = filter.toString().toLowerCase();
|
||||||
|
final ArrayList<ExpandableGroup> groups = new ArrayList<ExpandableGroup>();
|
||||||
for (int x = 0; x < expandableList.groups.size(); ++x) {
|
for (int x = 0; x < expandableListOriginal.groups.size(); ++x) {
|
||||||
final ExpandableGroup expandableGroup = expandableList.groups.get(x);
|
final ExpandableGroup expandableGroup = expandableListOriginal.groups.get(x);
|
||||||
final List<FollowModel> items = expandableGroup.getItems(false);
|
final String title = expandableGroup.getTitle();
|
||||||
final int itemCount = expandableGroup.getItemCount(false);
|
final List<User> items = expandableGroup.getItems();
|
||||||
|
if (items != null) {
|
||||||
for (int i = 0; i < itemCount; ++i) {
|
final List<User> toReturn = items.stream()
|
||||||
final FollowModel followModel = items.get(i);
|
.filter(u -> hasKey(query, u.getUsername(), u.getFullName()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
if (isFilterEmpty) followModel.setShown(true);
|
groups.add(new ExpandableGroup(title, toReturn));
|
||||||
else followModel.setShown(hasKey(query, followModel.getUsername(), followModel.getFullName()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
final FilterResults filterResults = new FilterResults();
|
||||||
return null;
|
filterResults.values = new ExpandableList(groups, expandableList.expandedGroupIndexes);
|
||||||
|
return filterResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean hasKey(final String key, final String username, final String name) {
|
private boolean hasKey(final String key, final String username, final String name) {
|
||||||
@ -60,15 +66,20 @@ public final class FollowAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void publishResults(final CharSequence constraint, final FilterResults results) {
|
protected void publishResults(final CharSequence constraint, final FilterResults results) {
|
||||||
|
if (results == null) {
|
||||||
|
expandableList = expandableListOriginal;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
final ExpandableList filteredList = (ExpandableList) results.values;
|
||||||
|
expandableList = filteredList;
|
||||||
|
}
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
private final View.OnClickListener onClickListener;
|
|
||||||
private final ExpandableList expandableList;
|
|
||||||
private final boolean hasManyGroups;
|
|
||||||
|
|
||||||
public FollowAdapter(final View.OnClickListener onClickListener, @NonNull final ArrayList<ExpandableGroup> groups) {
|
public FollowAdapter(final View.OnClickListener onClickListener, @NonNull final ArrayList<ExpandableGroup> groups) {
|
||||||
this.expandableList = new ExpandableList(groups);
|
this.expandableListOriginal = new ExpandableList(groups);
|
||||||
|
expandableList = this.expandableListOriginal;
|
||||||
this.onClickListener = onClickListener;
|
this.onClickListener = onClickListener;
|
||||||
this.hasManyGroups = groups.size() > 1;
|
this.hasManyGroups = groups.size() > 1;
|
||||||
}
|
}
|
||||||
@ -104,7 +115,7 @@ public final class FollowAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
|
|||||||
gvh.toggle(isGroupExpanded(group));
|
gvh.toggle(isGroupExpanded(group));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final FollowModel model = group.getItems(true).get(hasManyGroups ? listPos.childPos : position);
|
final User model = group.getItems().get(hasManyGroups ? listPos.childPos : position);
|
||||||
((FollowsViewHolder) holder).bind(model, onClickListener);
|
((FollowsViewHolder) holder).bind(model, onClickListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,7 +135,7 @@ public final class FollowAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
|
|||||||
|
|
||||||
final int groupPos = listPosition.groupPos;
|
final int groupPos = listPosition.groupPos;
|
||||||
final int positionStart = expandableList.getFlattenedGroupIndex(listPosition) + 1;
|
final int positionStart = expandableList.getFlattenedGroupIndex(listPosition) + 1;
|
||||||
final int positionEnd = expandableList.groups.get(groupPos).getItemCount(true);
|
final int positionEnd = expandableList.groups.get(groupPos).getItemCount();
|
||||||
|
|
||||||
final boolean isExpanded = expandableList.expandedGroupIndexes[groupPos];
|
final boolean isExpanded = expandableList.expandedGroupIndexes[groupPos];
|
||||||
expandableList.expandedGroupIndexes[groupPos] = !isExpanded;
|
expandableList.expandedGroupIndexes[groupPos] = !isExpanded;
|
||||||
|
@ -6,7 +6,6 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import awais.instagrabber.databinding.ItemFollowBinding;
|
import awais.instagrabber.databinding.ItemFollowBinding;
|
||||||
import awais.instagrabber.models.FollowModel;
|
|
||||||
import awais.instagrabber.repositories.responses.User;
|
import awais.instagrabber.repositories.responses.User;
|
||||||
|
|
||||||
public final class FollowsViewHolder extends RecyclerView.ViewHolder {
|
public final class FollowsViewHolder extends RecyclerView.ViewHolder {
|
||||||
@ -27,14 +26,4 @@ public final class FollowsViewHolder extends RecyclerView.ViewHolder {
|
|||||||
binding.fullName.setText(model.getFullName());
|
binding.fullName.setText(model.getFullName());
|
||||||
binding.profilePic.setImageURI(model.getProfilePicUrl());
|
binding.profilePic.setImageURI(model.getProfilePicUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void bind(final FollowModel model,
|
|
||||||
final View.OnClickListener onClickListener) {
|
|
||||||
if (model == null) return;
|
|
||||||
itemView.setTag(model);
|
|
||||||
itemView.setOnClickListener(onClickListener);
|
|
||||||
binding.username.setUsername("@" + model.getUsername());
|
|
||||||
binding.fullName.setText(model.getFullName());
|
|
||||||
binding.profilePic.setImageURI(model.getProfilePicUrl());
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,453 +1,260 @@
|
|||||||
package awais.instagrabber.fragments;
|
package awais.instagrabber.fragments
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources
|
||||||
import android.os.Bundle;
|
import android.os.Bundle
|
||||||
import android.util.Log;
|
import android.util.Log
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater
|
||||||
import android.view.Menu;
|
import android.view.Menu
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem
|
||||||
import android.view.View;
|
import android.view.View
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup
|
||||||
import android.widget.Toast;
|
import android.widget.Toast
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable
|
||||||
import androidx.appcompat.app.ActionBar;
|
import androidx.appcompat.app.ActionBar
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.widget.SearchView;
|
import androidx.appcompat.widget.SearchView
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.navigation.fragment.NavHostFragment;
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.navigation.fragment.NavHostFragment
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList
|
||||||
|
|
||||||
import awais.instagrabber.R;
|
import awais.instagrabber.R
|
||||||
import awais.instagrabber.adapters.FollowAdapter;
|
import awais.instagrabber.adapters.FollowAdapter
|
||||||
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
|
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader
|
||||||
import awais.instagrabber.databinding.FragmentFollowersViewerBinding;
|
import awais.instagrabber.databinding.FragmentFollowersViewerBinding
|
||||||
import awais.instagrabber.models.FollowModel;
|
import awais.instagrabber.models.Resource
|
||||||
import awais.instagrabber.repositories.responses.FriendshipListFetchResponse;
|
import awais.instagrabber.repositories.responses.User
|
||||||
import awais.instagrabber.utils.AppExecutors;
|
import awais.instagrabber.utils.AppExecutors
|
||||||
import awais.instagrabber.utils.CoroutineUtilsKt;
|
import awais.instagrabber.viewmodels.FollowViewModel
|
||||||
import awais.instagrabber.utils.TextUtils;
|
import thoughtbot.expandableadapter.ExpandableGroup
|
||||||
import awais.instagrabber.webservices.FriendshipRepository;
|
|
||||||
import awais.instagrabber.webservices.ServiceCallback;
|
|
||||||
import kotlinx.coroutines.Dispatchers;
|
|
||||||
import thoughtbot.expandableadapter.ExpandableGroup;
|
|
||||||
|
|
||||||
public final class FollowViewerFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
|
class FollowViewerFragment : Fragment(), SwipeRefreshLayout.OnRefreshListener {
|
||||||
private static final String TAG = "FollowViewerFragment";
|
private val followModels: ArrayList<User> = ArrayList<User>()
|
||||||
|
private val followingModels: ArrayList<User> = ArrayList<User>()
|
||||||
|
private val followersModels: ArrayList<User> = ArrayList<User>()
|
||||||
|
private val allFollowing: ArrayList<User> = ArrayList<User>()
|
||||||
|
private val moreAvailable = true
|
||||||
|
private var isFollowersList = false
|
||||||
|
private var isCompare = false
|
||||||
|
private var shouldRefresh = true
|
||||||
|
private var searching = false
|
||||||
|
private var profileId: Long = 0
|
||||||
|
private var username: String? = null
|
||||||
|
private var namePost: String? = null
|
||||||
|
private var type = 0
|
||||||
|
private var root: SwipeRefreshLayout? = null
|
||||||
|
private var adapter: FollowAdapter? = null
|
||||||
|
private lateinit var lazyLoader: RecyclerLazyLoader
|
||||||
|
private lateinit var fragmentActivity: AppCompatActivity
|
||||||
|
private lateinit var viewModel: FollowViewModel
|
||||||
|
private lateinit var binding: FragmentFollowersViewerBinding
|
||||||
|
|
||||||
private final ArrayList<FollowModel> followModels = new ArrayList<>();
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
private final ArrayList<FollowModel> followingModels = new ArrayList<>();
|
super.onCreate(savedInstanceState)
|
||||||
private final ArrayList<FollowModel> followersModels = new ArrayList<>();
|
fragmentActivity = activity as AppCompatActivity
|
||||||
private final ArrayList<FollowModel> allFollowing = new ArrayList<>();
|
viewModel = ViewModelProvider(this).get(FollowViewModel::class.java)
|
||||||
|
setHasOptionsMenu(true)
|
||||||
private boolean moreAvailable = true, isFollowersList, isCompare = false, loading = false, shouldRefresh = true, searching = false;
|
|
||||||
private long profileId;
|
|
||||||
private String username;
|
|
||||||
private String namePost;
|
|
||||||
private String type;
|
|
||||||
private String endCursor;
|
|
||||||
private Resources resources;
|
|
||||||
private LinearLayoutManager layoutManager;
|
|
||||||
private RecyclerLazyLoader lazyLoader;
|
|
||||||
private FollowModel model;
|
|
||||||
private FollowAdapter adapter;
|
|
||||||
private View.OnClickListener clickListener;
|
|
||||||
private FragmentFollowersViewerBinding binding;
|
|
||||||
private SwipeRefreshLayout root;
|
|
||||||
private FriendshipRepository friendshipRepository;
|
|
||||||
private AppCompatActivity fragmentActivity;
|
|
||||||
|
|
||||||
final ServiceCallback<FriendshipListFetchResponse> followingFetchCb = new ServiceCallback<FriendshipListFetchResponse>() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess(final FriendshipListFetchResponse result) {
|
|
||||||
if (result != null && isCompare) {
|
|
||||||
followingModels.addAll(result.getItems());
|
|
||||||
if (!isFollowersList) followModels.addAll(result.getItems());
|
|
||||||
if (result.isMoreAvailable()) {
|
|
||||||
endCursor = result.getNextMaxId();
|
|
||||||
friendshipRepository.getList(
|
|
||||||
false,
|
|
||||||
profileId,
|
|
||||||
endCursor,
|
|
||||||
CoroutineUtilsKt.getContinuation((response, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> {
|
|
||||||
if (throwable != null) {
|
|
||||||
onFailure(throwable);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
onSuccess(response);
|
|
||||||
}), Dispatchers.getIO())
|
|
||||||
);
|
|
||||||
} else if (followersModels.size() == 0) {
|
|
||||||
if (!isFollowersList) moreAvailable = false;
|
|
||||||
friendshipRepository.getList(
|
|
||||||
true,
|
|
||||||
profileId,
|
|
||||||
null,
|
|
||||||
CoroutineUtilsKt.getContinuation((response, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> {
|
|
||||||
if (throwable != null) {
|
|
||||||
followingFetchCb.onFailure(throwable);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
followingFetchCb.onSuccess(response);
|
|
||||||
}), Dispatchers.getIO())
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
if (!isFollowersList) moreAvailable = false;
|
|
||||||
showCompare();
|
|
||||||
}
|
|
||||||
} else if (isCompare) binding.swipeRefreshLayout.setRefreshing(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
override fun onCreateView(
|
||||||
public void onFailure(final Throwable t) {
|
inflater: LayoutInflater,
|
||||||
try {
|
container: ViewGroup?,
|
||||||
binding.swipeRefreshLayout.setRefreshing(false);
|
savedInstanceState: Bundle?
|
||||||
Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show();
|
): View {
|
||||||
} catch (Throwable ignored) {}
|
|
||||||
Log.e(TAG, "Error fetching list (double, following)", t);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
final ServiceCallback<FriendshipListFetchResponse> followersFetchCb = new ServiceCallback<FriendshipListFetchResponse>() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess(final FriendshipListFetchResponse result) {
|
|
||||||
if (result != null && isCompare) {
|
|
||||||
followersModels.addAll(result.getItems());
|
|
||||||
if (isFollowersList) followModels.addAll(result.getItems());
|
|
||||||
if (result.isMoreAvailable()) {
|
|
||||||
endCursor = result.getNextMaxId();
|
|
||||||
friendshipRepository.getList(
|
|
||||||
true,
|
|
||||||
profileId,
|
|
||||||
endCursor,
|
|
||||||
CoroutineUtilsKt.getContinuation((response, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> {
|
|
||||||
if (throwable != null) {
|
|
||||||
onFailure(throwable);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
onSuccess(response);
|
|
||||||
}), Dispatchers.getIO())
|
|
||||||
);
|
|
||||||
} else if (followingModels.size() == 0) {
|
|
||||||
if (isFollowersList) moreAvailable = false;
|
|
||||||
friendshipRepository.getList(
|
|
||||||
false,
|
|
||||||
profileId,
|
|
||||||
null,
|
|
||||||
CoroutineUtilsKt.getContinuation((response, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> {
|
|
||||||
if (throwable != null) {
|
|
||||||
followingFetchCb.onFailure(throwable);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
followingFetchCb.onSuccess(response);
|
|
||||||
}), Dispatchers.getIO())
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
if (isFollowersList) moreAvailable = false;
|
|
||||||
showCompare();
|
|
||||||
}
|
|
||||||
} else if (isCompare) binding.swipeRefreshLayout.setRefreshing(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(final Throwable t) {
|
|
||||||
try {
|
|
||||||
binding.swipeRefreshLayout.setRefreshing(false);
|
|
||||||
Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show();
|
|
||||||
} catch (Throwable ignored) {}
|
|
||||||
Log.e(TAG, "Error fetching list (double, follower)", t);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
friendshipRepository = FriendshipRepository.Companion.getInstance();
|
|
||||||
fragmentActivity = (AppCompatActivity) getActivity();
|
|
||||||
setHasOptionsMenu(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) {
|
|
||||||
if (root != null) {
|
if (root != null) {
|
||||||
shouldRefresh = false;
|
shouldRefresh = false
|
||||||
return root;
|
return root!!
|
||||||
}
|
}
|
||||||
binding = FragmentFollowersViewerBinding.inflate(getLayoutInflater());
|
binding = FragmentFollowersViewerBinding.inflate(layoutInflater)
|
||||||
root = binding.getRoot();
|
root = binding.root
|
||||||
return root;
|
return root!!
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
|
if (!shouldRefresh) return
|
||||||
if (!shouldRefresh) return;
|
init()
|
||||||
init();
|
shouldRefresh = false
|
||||||
shouldRefresh = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void init() {
|
private fun init() {
|
||||||
if (getArguments() == null) return;
|
val args = arguments ?: return
|
||||||
final FollowViewerFragmentArgs fragmentArgs = FollowViewerFragmentArgs.fromBundle(getArguments());
|
val fragmentArgs = FollowViewerFragmentArgs.fromBundle(args)
|
||||||
profileId = fragmentArgs.getProfileId();
|
viewModel.userId.value = fragmentArgs.profileId
|
||||||
isFollowersList = fragmentArgs.getIsFollowersList();
|
isFollowersList = fragmentArgs.isFollowersList
|
||||||
username = fragmentArgs.getUsername();
|
username = fragmentArgs.username
|
||||||
namePost = username;
|
namePost = username
|
||||||
if (TextUtils.isEmpty(username)) {
|
setTitle(username)
|
||||||
// this usually should not occur
|
binding.swipeRefreshLayout.setOnRefreshListener(this)
|
||||||
username = "You";
|
if (isCompare) listCompare() else listFollows()
|
||||||
namePost = "You're";
|
viewModel.fetch(isFollowersList, null)
|
||||||
}
|
|
||||||
setTitle(username);
|
|
||||||
resources = getResources();
|
|
||||||
clickListener = v -> {
|
|
||||||
final Object tag = v.getTag();
|
|
||||||
if (tag instanceof FollowModel) {
|
|
||||||
model = (FollowModel) tag;
|
|
||||||
final FollowViewerFragmentDirections.ActionFollowViewerFragmentToProfileFragment action = FollowViewerFragmentDirections
|
|
||||||
.actionFollowViewerFragmentToProfileFragment();
|
|
||||||
action.setUsername("@" + model.getUsername());
|
|
||||||
NavHostFragment.findNavController(this).navigate(action);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
binding.swipeRefreshLayout.setOnRefreshListener(this);
|
|
||||||
onRefresh();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
override fun onResume() {
|
||||||
public void onResume() {
|
super.onResume()
|
||||||
super.onResume();
|
setTitle(username)
|
||||||
setTitle(username);
|
setSubtitle(type)
|
||||||
setSubtitle(type);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setTitle(final String title) {
|
private fun setTitle(title: String?) {
|
||||||
final ActionBar actionBar = fragmentActivity.getSupportActionBar();
|
val actionBar: ActionBar = fragmentActivity.supportActionBar ?: return
|
||||||
if (actionBar == null) return;
|
actionBar.title = title
|
||||||
actionBar.setTitle(title);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setSubtitle(final String subtitle) {
|
private fun setSubtitle(subtitleRes: Int) {
|
||||||
final ActionBar actionBar = fragmentActivity.getSupportActionBar();
|
val actionBar: ActionBar = fragmentActivity.supportActionBar ?: return
|
||||||
if (actionBar == null) return;
|
actionBar.setSubtitle(subtitleRes)
|
||||||
actionBar.setSubtitle(subtitle);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setSubtitle(@SuppressWarnings("SameParameterValue") final int subtitleRes) {
|
override fun onRefresh() {
|
||||||
final ActionBar actionBar = fragmentActivity.getSupportActionBar();
|
lazyLoader.resetState()
|
||||||
if (actionBar == null) return;
|
viewModel.clearProgress()
|
||||||
actionBar.setSubtitle(subtitleRes);
|
if (isCompare) listCompare()
|
||||||
|
else viewModel.fetch(isFollowersList, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private fun listFollows() {
|
||||||
public void onRefresh() {
|
viewModel.comparison.removeObservers(viewLifecycleOwner)
|
||||||
if (isCompare) listCompare();
|
viewModel.status.removeObservers(viewLifecycleOwner)
|
||||||
else listFollows();
|
type = if (isFollowersList) R.string.followers_type_followers else R.string.followers_type_following
|
||||||
endCursor = null;
|
setSubtitle(type)
|
||||||
lazyLoader.resetState();
|
val layoutManager = LinearLayoutManager(context)
|
||||||
|
lazyLoader = RecyclerLazyLoader(layoutManager, { _, totalItemsCount ->
|
||||||
|
binding.swipeRefreshLayout.isRefreshing = true
|
||||||
|
val liveData = if (searching) viewModel.search(isFollowersList)
|
||||||
|
else viewModel.fetch(isFollowersList, null)
|
||||||
|
liveData.observe(viewLifecycleOwner) {
|
||||||
|
binding.swipeRefreshLayout.isRefreshing = it.status != Resource.Status.SUCCESS
|
||||||
|
layoutManager.scrollToPosition(totalItemsCount)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
binding.rvFollow.addOnScrollListener(lazyLoader)
|
||||||
|
binding.rvFollow.layoutManager = layoutManager
|
||||||
|
viewModel.getList(isFollowersList).observe(viewLifecycleOwner) {
|
||||||
|
binding.swipeRefreshLayout.isRefreshing = false
|
||||||
|
refreshAdapter(it, null, null, null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void listFollows() {
|
private fun listCompare() {
|
||||||
type = resources.getString(isFollowersList ? R.string.followers_type_followers : R.string.followers_type_following);
|
viewModel.getList(isFollowersList).removeObservers(viewLifecycleOwner)
|
||||||
setSubtitle(type);
|
binding.rvFollow.clearOnScrollListeners()
|
||||||
final ServiceCallback<FriendshipListFetchResponse> cb = new ServiceCallback<FriendshipListFetchResponse>() {
|
binding.swipeRefreshLayout.isRefreshing = true
|
||||||
@Override
|
setSubtitle(R.string.followers_compare)
|
||||||
public void onSuccess(final FriendshipListFetchResponse result) {
|
viewModel.status.observe(viewLifecycleOwner) {}
|
||||||
if (result == null) {
|
viewModel.comparison.observe(viewLifecycleOwner) {
|
||||||
binding.swipeRefreshLayout.setRefreshing(false);
|
if (it != null) {
|
||||||
return;
|
binding.swipeRefreshLayout.isRefreshing = false
|
||||||
|
refreshAdapter(null, it.first, it.second, it.third)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
int oldSize = followModels.size() == 0 ? 0 : followModels.size() - 1;
|
|
||||||
followModels.addAll(result.getItems());
|
|
||||||
if (result.isMoreAvailable()) {
|
|
||||||
moreAvailable = true;
|
|
||||||
endCursor = result.getNextMaxId();
|
|
||||||
} else moreAvailable = false;
|
|
||||||
binding.swipeRefreshLayout.setRefreshing(false);
|
|
||||||
if (isFollowersList) followersModels.addAll(result.getItems());
|
|
||||||
else followingModels.addAll(result.getItems());
|
|
||||||
refreshAdapter(followModels, null, null, null);
|
|
||||||
layoutManager.scrollToPosition(oldSize);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||||
public void onFailure(final Throwable t) {
|
inflater.inflate(R.menu.follow, menu)
|
||||||
try {
|
val menuSearch = menu.findItem(R.id.action_search)
|
||||||
binding.swipeRefreshLayout.setRefreshing(false);
|
val searchView = menuSearch.actionView as SearchView
|
||||||
Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show();
|
searchView.queryHint = resources.getString(R.string.action_search)
|
||||||
} catch (Throwable ignored) {}
|
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||||
Log.e(TAG, "Error fetching list (single)", t);
|
override fun onQueryTextSubmit(query: String): Boolean {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
};
|
|
||||||
layoutManager = new LinearLayoutManager(getContext());
|
override fun onQueryTextChange(query: String): Boolean {
|
||||||
lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
|
if (query.isNullOrEmpty()) {
|
||||||
if (!TextUtils.isEmpty(endCursor) && !searching) {
|
if (!isCompare && searching) {
|
||||||
binding.swipeRefreshLayout.setRefreshing(true);
|
viewModel.setQuery(null, isFollowersList)
|
||||||
layoutManager.setStackFromEnd(true);
|
viewModel.getSearch().removeObservers(viewLifecycleOwner)
|
||||||
friendshipRepository.getList(
|
viewModel.getList(isFollowersList).observe(viewLifecycleOwner) {
|
||||||
isFollowersList,
|
refreshAdapter(it, null, null, null)
|
||||||
profileId,
|
|
||||||
endCursor,
|
|
||||||
CoroutineUtilsKt.getContinuation((response, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> {
|
|
||||||
if (throwable != null) {
|
|
||||||
cb.onFailure(throwable);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
cb.onSuccess(response);
|
|
||||||
}), Dispatchers.getIO())
|
|
||||||
);
|
|
||||||
endCursor = null;
|
|
||||||
}
|
}
|
||||||
});
|
searching = false
|
||||||
binding.rvFollow.addOnScrollListener(lazyLoader);
|
return true
|
||||||
binding.rvFollow.setLayoutManager(layoutManager);
|
|
||||||
if (moreAvailable) {
|
|
||||||
binding.swipeRefreshLayout.setRefreshing(true);
|
|
||||||
friendshipRepository.getList(
|
|
||||||
isFollowersList,
|
|
||||||
profileId,
|
|
||||||
endCursor,
|
|
||||||
CoroutineUtilsKt.getContinuation((response, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> {
|
|
||||||
if (throwable != null) {
|
|
||||||
cb.onFailure(throwable);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
cb.onSuccess(response);
|
searching = true
|
||||||
}), Dispatchers.getIO())
|
if (isCompare && adapter != null) {
|
||||||
);
|
adapter!!.filter.filter(query)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
viewModel.getList(isFollowersList).removeObservers(viewLifecycleOwner)
|
||||||
|
binding.swipeRefreshLayout.isRefreshing = true
|
||||||
|
viewModel.setQuery(query, isFollowersList)
|
||||||
|
viewModel.getSearch().observe(viewLifecycleOwner) {
|
||||||
|
binding.swipeRefreshLayout.isRefreshing = false
|
||||||
|
refreshAdapter(it, null, null, null)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
if (item.itemId != R.id.action_compare) return super.onOptionsItemSelected(item)
|
||||||
|
binding.rvFollow.adapter = null
|
||||||
|
if (isCompare) {
|
||||||
|
isCompare = false
|
||||||
|
listFollows()
|
||||||
} else {
|
} else {
|
||||||
refreshAdapter(followModels, null, null, null);
|
isCompare = true
|
||||||
layoutManager.scrollToPosition(0);
|
listCompare()
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private void listCompare() {
|
private fun refreshAdapter(
|
||||||
layoutManager.setStackFromEnd(false);
|
followModels: List<User>?,
|
||||||
binding.rvFollow.clearOnScrollListeners();
|
allFollowing: List<User>?,
|
||||||
loading = true;
|
followingModels: List<User>?,
|
||||||
setSubtitle(R.string.followers_compare);
|
followersModels: List<User>?
|
||||||
allFollowing.clear();
|
) {
|
||||||
if (moreAvailable) {
|
val groups: ArrayList<ExpandableGroup> = ArrayList<ExpandableGroup>(1)
|
||||||
binding.swipeRefreshLayout.setRefreshing(true);
|
|
||||||
Toast.makeText(getContext(), R.string.follower_start_compare, Toast.LENGTH_LONG).show();
|
|
||||||
friendshipRepository.getList(
|
|
||||||
isFollowersList,
|
|
||||||
profileId,
|
|
||||||
endCursor,
|
|
||||||
CoroutineUtilsKt.getContinuation((response, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> {
|
|
||||||
final ServiceCallback<FriendshipListFetchResponse> callback = isFollowersList ? followersFetchCb : followingFetchCb;
|
|
||||||
if (throwable != null) {
|
|
||||||
callback.onFailure(throwable);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
callback.onSuccess(response);
|
|
||||||
}), Dispatchers.getIO())
|
|
||||||
);
|
|
||||||
} else if (followersModels.size() == 0 || followingModels.size() == 0) {
|
|
||||||
binding.swipeRefreshLayout.setRefreshing(true);
|
|
||||||
Toast.makeText(getContext(), R.string.follower_start_compare, Toast.LENGTH_LONG).show();
|
|
||||||
friendshipRepository.getList(
|
|
||||||
!isFollowersList,
|
|
||||||
profileId,
|
|
||||||
null,
|
|
||||||
CoroutineUtilsKt.getContinuation((response, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> {
|
|
||||||
final ServiceCallback<FriendshipListFetchResponse> callback = isFollowersList ? followingFetchCb : followersFetchCb;
|
|
||||||
if (throwable != null) {
|
|
||||||
callback.onFailure(throwable);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
callback.onSuccess(response);
|
|
||||||
}), Dispatchers.getIO()));
|
|
||||||
} 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
|
|
||||||
public void onCreateOptionsMenu(@NonNull final Menu menu, final MenuInflater inflater) {
|
|
||||||
inflater.inflate(R.menu.follow, menu);
|
|
||||||
final MenuItem menuSearch = menu.findItem(R.id.action_search);
|
|
||||||
final SearchView searchView = (SearchView) menuSearch.getActionView();
|
|
||||||
searchView.setQueryHint(getResources().getString(R.string.action_search));
|
|
||||||
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onQueryTextSubmit(final String query) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onQueryTextChange(final String query) {
|
|
||||||
if (TextUtils.isEmpty(query)) {
|
|
||||||
searching = false;
|
|
||||||
// refreshAdapter(followModels, followingModels, followersModels, allFollowing);
|
|
||||||
}
|
|
||||||
// else filter.filter(query.toLowerCase());
|
|
||||||
if (adapter != null) {
|
|
||||||
searching = true;
|
|
||||||
adapter.getFilter().filter(query);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
|
|
||||||
if (item.getItemId() != R.id.action_compare) return super.onOptionsItemSelected(item);
|
|
||||||
binding.rvFollow.setAdapter(null);
|
|
||||||
final Context context = getContext();
|
|
||||||
if (loading) Toast.makeText(context, R.string.follower_wait_to_load, Toast.LENGTH_LONG).show();
|
|
||||||
else if (isCompare) {
|
|
||||||
isCompare = false;
|
|
||||||
listFollows();
|
|
||||||
} else {
|
|
||||||
isCompare = true;
|
|
||||||
listCompare();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void refreshAdapter(final ArrayList<FollowModel> followModels,
|
|
||||||
final ArrayList<FollowModel> followingModels,
|
|
||||||
final ArrayList<FollowModel> followersModels,
|
|
||||||
final ArrayList<FollowModel> allFollowing) {
|
|
||||||
loading = false;
|
|
||||||
final ArrayList<ExpandableGroup> groups = new ArrayList<>(1);
|
|
||||||
|
|
||||||
if (isCompare && followingModels != null && followersModels != null && allFollowing != null) {
|
if (isCompare && followingModels != null && followersModels != null && allFollowing != null) {
|
||||||
if (followingModels.size() > 0)
|
if (followingModels.size > 0) groups.add(
|
||||||
groups.add(new ExpandableGroup(resources.getString(R.string.followers_not_following, username), followingModels));
|
ExpandableGroup(
|
||||||
if (followersModels.size() > 0)
|
getString(
|
||||||
groups.add(new ExpandableGroup(resources.getString(R.string.followers_not_follower, namePost), followersModels));
|
R.string.followers_not_following,
|
||||||
if (allFollowing.size() > 0)
|
username
|
||||||
groups.add(new ExpandableGroup(resources.getString(R.string.followers_both_following), allFollowing));
|
), followingModels
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if (followersModels.size > 0) groups.add(
|
||||||
|
ExpandableGroup(
|
||||||
|
getString(
|
||||||
|
R.string.followers_not_follower,
|
||||||
|
namePost
|
||||||
|
), followersModels
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if (allFollowing.size > 0) groups.add(
|
||||||
|
ExpandableGroup(
|
||||||
|
getString(R.string.followers_both_following),
|
||||||
|
allFollowing
|
||||||
|
)
|
||||||
|
)
|
||||||
} else if (followModels != null) {
|
} else if (followModels != null) {
|
||||||
groups.add(new ExpandableGroup(type, followModels));
|
groups.add(ExpandableGroup(getString(type), followModels))
|
||||||
} else return;
|
} else return
|
||||||
adapter = new FollowAdapter(clickListener, groups);
|
adapter = FollowAdapter({ v ->
|
||||||
adapter.toggleGroup(0);
|
val tag = v.tag
|
||||||
binding.rvFollow.setAdapter(adapter);
|
if (tag is User) {
|
||||||
|
val model = tag
|
||||||
|
val bundle = Bundle()
|
||||||
|
bundle.putString("username", model.username)
|
||||||
|
NavHostFragment.findNavController(this).navigate(R.id.action_global_profileFragment, bundle)
|
||||||
|
}
|
||||||
|
}, groups)
|
||||||
|
adapter!!.toggleGroup(0)
|
||||||
|
binding.rvFollow.adapter = adapter!!
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "FollowViewerFragment"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,49 +0,0 @@
|
|||||||
package awais.instagrabber.models
|
|
||||||
|
|
||||||
import java.io.Serializable
|
|
||||||
|
|
||||||
class FollowModel(
|
|
||||||
val id: String,
|
|
||||||
val username: String,
|
|
||||||
val fullName: String,
|
|
||||||
val profilePicUrl: String
|
|
||||||
) : Serializable {
|
|
||||||
private var hasNextPage = false
|
|
||||||
get() = endCursor != null && field
|
|
||||||
|
|
||||||
var isShown = true
|
|
||||||
|
|
||||||
var endCursor: String? = null
|
|
||||||
private set
|
|
||||||
|
|
||||||
fun setPageCursor(hasNextPage: Boolean, endCursor: String?) {
|
|
||||||
this.endCursor = endCursor
|
|
||||||
this.hasNextPage = hasNextPage
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (javaClass != other?.javaClass) return false
|
|
||||||
|
|
||||||
other as FollowModel
|
|
||||||
|
|
||||||
if (id != other.id) return false
|
|
||||||
if (username != other.username) return false
|
|
||||||
if (fullName != other.fullName) return false
|
|
||||||
if (profilePicUrl != other.profilePicUrl) return false
|
|
||||||
if (isShown != other.isShown) return false
|
|
||||||
if (endCursor != other.endCursor) return false
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = id.hashCode()
|
|
||||||
result = 31 * result + username.hashCode()
|
|
||||||
result = 31 * result + fullName.hashCode()
|
|
||||||
result = 31 * result + profilePicUrl.hashCode()
|
|
||||||
result = 31 * result + isShown.hashCode()
|
|
||||||
result = 31 * result + (endCursor?.hashCode() ?: 0)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,7 @@
|
|||||||
package awais.instagrabber.repositories
|
package awais.instagrabber.repositories
|
||||||
|
|
||||||
import awais.instagrabber.repositories.responses.FriendshipChangeResponse
|
import awais.instagrabber.repositories.responses.FriendshipChangeResponse
|
||||||
|
import awais.instagrabber.repositories.responses.FriendshipListFetchResponse
|
||||||
import awais.instagrabber.repositories.responses.FriendshipRestrictResponse
|
import awais.instagrabber.repositories.responses.FriendshipRestrictResponse
|
||||||
import retrofit2.http.*
|
import retrofit2.http.*
|
||||||
|
|
||||||
@ -25,7 +26,7 @@ interface FriendshipService {
|
|||||||
@Path("userId") userId: Long,
|
@Path("userId") userId: Long,
|
||||||
@Path("type") type: String, // following or followers
|
@Path("type") type: String, // following or followers
|
||||||
@QueryMap(encoded = true) queryParams: Map<String, String>,
|
@QueryMap(encoded = true) queryParams: Map<String, String>,
|
||||||
): String
|
): FriendshipListFetchResponse
|
||||||
|
|
||||||
@FormUrlEncoded
|
@FormUrlEncoded
|
||||||
@POST("/api/v1/friendships/{action}/")
|
@POST("/api/v1/friendships/{action}/")
|
||||||
|
@ -1,27 +1,10 @@
|
|||||||
package awais.instagrabber.repositories.responses
|
package awais.instagrabber.repositories.responses
|
||||||
|
|
||||||
import awais.instagrabber.models.FollowModel
|
|
||||||
|
|
||||||
data class FriendshipListFetchResponse(
|
data class FriendshipListFetchResponse(
|
||||||
var nextMaxId: String?,
|
var nextMaxId: String?,
|
||||||
var status: String?,
|
var status: String?,
|
||||||
var items: List<FollowModel>?
|
var users: List<User>?
|
||||||
) {
|
) {
|
||||||
val isMoreAvailable: Boolean
|
val isMoreAvailable: Boolean
|
||||||
get() = !nextMaxId.isNullOrBlank()
|
get() = !nextMaxId.isNullOrBlank()
|
||||||
|
|
||||||
fun setNextMaxId(nextMaxId: String): FriendshipListFetchResponse {
|
|
||||||
this.nextMaxId = nextMaxId
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setStatus(status: String): FriendshipListFetchResponse {
|
|
||||||
this.status = status
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setItems(items: List<FollowModel>): FriendshipListFetchResponse {
|
|
||||||
this.items = items
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,19 +1,167 @@
|
|||||||
package awais.instagrabber.viewmodels;
|
package awais.instagrabber.viewmodels
|
||||||
|
|
||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.ViewModel;
|
import androidx.lifecycle.MediatorLiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import awais.instagrabber.models.Resource
|
||||||
|
import awais.instagrabber.repositories.responses.User
|
||||||
|
import awais.instagrabber.webservices.FriendshipRepository
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
import java.util.List;
|
class FollowViewModel : ViewModel() {
|
||||||
|
// data
|
||||||
|
val userId = MutableLiveData<Long>()
|
||||||
|
private val followers = MutableLiveData<List<User>>()
|
||||||
|
private val followings = MutableLiveData<List<User>>()
|
||||||
|
private val searchResults = MutableLiveData<List<User>>()
|
||||||
|
|
||||||
import awais.instagrabber.models.FollowModel;
|
// cursors
|
||||||
|
private val followersMaxId = MutableLiveData<String?>("")
|
||||||
|
private val followingMaxId = MutableLiveData<String?>("")
|
||||||
|
private val searchingMaxId = MutableLiveData<String?>("")
|
||||||
|
private val searchQuery = MutableLiveData<String?>()
|
||||||
|
|
||||||
public class FollowViewModel extends ViewModel {
|
// comparison
|
||||||
private MutableLiveData<List<FollowModel>> list;
|
val status: LiveData<Pair<Boolean, Boolean>> = object : MediatorLiveData<Pair<Boolean, Boolean>>() {
|
||||||
|
init {
|
||||||
public MutableLiveData<List<FollowModel>> getList() {
|
postValue(Pair(false, false))
|
||||||
if (list == null) {
|
addSource(followersMaxId) {
|
||||||
list = new MutableLiveData<>();
|
if (it == null) {
|
||||||
|
postValue(Pair(true, value!!.second))
|
||||||
}
|
}
|
||||||
return list;
|
else fetch(true, it)
|
||||||
|
}
|
||||||
|
addSource(followingMaxId) {
|
||||||
|
if (it == null) {
|
||||||
|
postValue(Pair(value!!.first, true))
|
||||||
|
}
|
||||||
|
else fetch(false, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val comparison: LiveData<Triple<List<User>, List<User>, List<User>>> =
|
||||||
|
object : MediatorLiveData<Triple<List<User>, List<User>, List<User>>>() {
|
||||||
|
init {
|
||||||
|
addSource(status) {
|
||||||
|
if (it.first && it.second) {
|
||||||
|
val followersList = followers.value!!
|
||||||
|
val followingList = followings.value!!
|
||||||
|
val allUsers: MutableList<User> = mutableListOf()
|
||||||
|
allUsers.addAll(followersList)
|
||||||
|
allUsers.addAll(followingList)
|
||||||
|
val followersMap = followersList.groupBy { it.pk }
|
||||||
|
val followingMap = followingList.groupBy { it.pk }
|
||||||
|
val mutual: MutableList<User> = mutableListOf()
|
||||||
|
val onlyFollowing: MutableList<User> = mutableListOf()
|
||||||
|
val onlyFollowers: MutableList<User> = mutableListOf()
|
||||||
|
allUsers.forEach {
|
||||||
|
val isFollowing = followingMap.get(it.pk) != null
|
||||||
|
val isFollower = followersMap.get(it.pk) != null
|
||||||
|
if (isFollowing && isFollower) mutual.add(it)
|
||||||
|
else if (isFollowing) onlyFollowing.add(it)
|
||||||
|
else if (isFollower) onlyFollowers.add(it)
|
||||||
|
}
|
||||||
|
postValue(Triple(mutual, onlyFollowing, onlyFollowers))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val friendshipRepository: FriendshipRepository by lazy { FriendshipRepository.getInstance() }
|
||||||
|
|
||||||
|
// fetch: supply max ID for continuous fetch
|
||||||
|
fun fetch(follower: Boolean, nextMaxId: String?): LiveData<Resource<Any?>> {
|
||||||
|
val data = MutableLiveData<Resource<Any?>>()
|
||||||
|
data.postValue(Resource.loading(null))
|
||||||
|
val maxId = if (follower) followersMaxId else followingMaxId
|
||||||
|
if (maxId.value == null && nextMaxId == null) data.postValue(Resource.success(null))
|
||||||
|
else if (userId.value == null) data.postValue(Resource.error("No user ID supplied!", null))
|
||||||
|
else viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val tempList = friendshipRepository.getList(
|
||||||
|
follower,
|
||||||
|
userId.value!!,
|
||||||
|
nextMaxId ?: maxId.value,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
if (!tempList.status.equals("ok")) {
|
||||||
|
data.postValue(Resource.error("Status not ok!", null))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (tempList.users != null) {
|
||||||
|
val liveData = if (follower) followers else followings
|
||||||
|
val currentList = if (liveData.value != null) liveData.value!!.toMutableList()
|
||||||
|
else mutableListOf()
|
||||||
|
currentList.addAll(tempList.users!!)
|
||||||
|
liveData.postValue(currentList.toList())
|
||||||
|
}
|
||||||
|
maxId.postValue(tempList.nextMaxId)
|
||||||
|
data.postValue(Resource.success(null))
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
data.postValue(Resource.error(e.message, null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getList(follower: Boolean): LiveData<List<User>> {
|
||||||
|
return if (follower) followers else followings
|
||||||
|
}
|
||||||
|
|
||||||
|
fun search(follower: Boolean): LiveData<Resource<Any?>> {
|
||||||
|
val data = MutableLiveData<Resource<Any?>>()
|
||||||
|
data.postValue(Resource.loading(null))
|
||||||
|
val query = searchQuery.value
|
||||||
|
if (searchingMaxId.value == null) data.postValue(Resource.success(null))
|
||||||
|
else if (userId.value == null) data.postValue(Resource.error("No user ID supplied!", null))
|
||||||
|
else if (query.isNullOrEmpty()) data.postValue(Resource.error("No query supplied!", null))
|
||||||
|
else viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val tempList = friendshipRepository.getList(
|
||||||
|
follower,
|
||||||
|
userId.value!!,
|
||||||
|
searchingMaxId.value,
|
||||||
|
query
|
||||||
|
)
|
||||||
|
if (!tempList.status.equals("ok")) {
|
||||||
|
data.postValue(Resource.error("Status not ok!", null))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (tempList.users != null) {
|
||||||
|
val currentList = if (searchResults.value != null) searchResults.value!!.toMutableList()
|
||||||
|
else mutableListOf()
|
||||||
|
currentList.addAll(tempList.users!!)
|
||||||
|
searchResults.postValue(currentList.toList())
|
||||||
|
}
|
||||||
|
searchingMaxId.postValue(tempList.nextMaxId)
|
||||||
|
data.postValue(Resource.success(null))
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
data.postValue(Resource.error(e.message, null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSearch(): LiveData<List<User>> {
|
||||||
|
return searchResults
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setQuery(query: String?, follower: Boolean) {
|
||||||
|
searchQuery.value = query
|
||||||
|
if (!query.isNullOrEmpty()) search(follower)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearProgress() {
|
||||||
|
followersMaxId.value = ""
|
||||||
|
followingMaxId.value = ""
|
||||||
|
searchingMaxId.value = ""
|
||||||
|
followings.value = listOf<User>()
|
||||||
|
followers.value = listOf<User>()
|
||||||
|
searchResults.value = listOf<User>()
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,15 +1,11 @@
|
|||||||
package awais.instagrabber.webservices
|
package awais.instagrabber.webservices
|
||||||
|
|
||||||
import awais.instagrabber.models.FollowModel
|
|
||||||
import awais.instagrabber.repositories.FriendshipService
|
import awais.instagrabber.repositories.FriendshipService
|
||||||
import awais.instagrabber.repositories.responses.FriendshipChangeResponse
|
import awais.instagrabber.repositories.responses.FriendshipChangeResponse
|
||||||
import awais.instagrabber.repositories.responses.FriendshipListFetchResponse
|
import awais.instagrabber.repositories.responses.FriendshipListFetchResponse
|
||||||
import awais.instagrabber.repositories.responses.FriendshipRestrictResponse
|
import awais.instagrabber.repositories.responses.FriendshipRestrictResponse
|
||||||
import awais.instagrabber.utils.Utils
|
import awais.instagrabber.utils.Utils
|
||||||
import awais.instagrabber.webservices.RetrofitFactory.retrofit
|
import awais.instagrabber.webservices.RetrofitFactory.retrofit
|
||||||
import org.json.JSONArray
|
|
||||||
import org.json.JSONException
|
|
||||||
import org.json.JSONObject
|
|
||||||
|
|
||||||
class FriendshipRepository(private val service: FriendshipService) {
|
class FriendshipRepository(private val service: FriendshipService) {
|
||||||
|
|
||||||
@ -113,43 +109,12 @@ class FriendshipRepository(private val service: FriendshipService) {
|
|||||||
follower: Boolean,
|
follower: Boolean,
|
||||||
targetUserId: Long,
|
targetUserId: Long,
|
||||||
maxId: String?,
|
maxId: String?,
|
||||||
|
query: String?
|
||||||
): FriendshipListFetchResponse {
|
): FriendshipListFetchResponse {
|
||||||
val queryMap = if (maxId != null) mapOf("max_id" to maxId) else emptyMap()
|
val queryMap: MutableMap<String, String> = mutableMapOf()
|
||||||
val response = service.getList(targetUserId, if (follower) "followers" else "following", queryMap)
|
if (!maxId.isNullOrEmpty()) queryMap.set("max_id", maxId)
|
||||||
return parseListResponse(response)
|
if (!query.isNullOrEmpty()) queryMap.set("query", query)
|
||||||
}
|
return service.getList(targetUserId, if (follower) "followers" else "following", queryMap.toMap())
|
||||||
|
|
||||||
@Throws(JSONException::class)
|
|
||||||
private fun parseListResponse(body: String): FriendshipListFetchResponse {
|
|
||||||
val root = JSONObject(body)
|
|
||||||
val nextMaxId = root.optString("next_max_id")
|
|
||||||
val status = root.optString("status")
|
|
||||||
val itemsJson = root.optJSONArray("users")
|
|
||||||
val items = parseItems(itemsJson)
|
|
||||||
return FriendshipListFetchResponse(
|
|
||||||
nextMaxId,
|
|
||||||
status,
|
|
||||||
items
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(JSONException::class)
|
|
||||||
private fun parseItems(items: JSONArray?): List<FollowModel> {
|
|
||||||
if (items == null) {
|
|
||||||
return emptyList()
|
|
||||||
}
|
|
||||||
val followModels = mutableListOf<FollowModel>()
|
|
||||||
for (i in 0 until items.length()) {
|
|
||||||
val itemJson = items.optJSONObject(i) ?: continue
|
|
||||||
val followModel = FollowModel(
|
|
||||||
itemJson.getString("pk"),
|
|
||||||
itemJson.getString("username"),
|
|
||||||
itemJson.optString("full_name"),
|
|
||||||
itemJson.getString("profile_pic_url")
|
|
||||||
)
|
|
||||||
followModels.add(followModel)
|
|
||||||
}
|
|
||||||
return followModels
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -3,13 +3,13 @@ package thoughtbot.expandableadapter;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import awais.instagrabber.models.FollowModel;
|
import awais.instagrabber.repositories.responses.User;
|
||||||
|
|
||||||
public class ExpandableGroup {
|
public class ExpandableGroup {
|
||||||
private final String title;
|
private final String title;
|
||||||
private final List<FollowModel> items;
|
private final List<User> items;
|
||||||
|
|
||||||
public ExpandableGroup(final String title, final List<FollowModel> items) {
|
public ExpandableGroup(final String title, final List<User> items) {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.items = items;
|
this.items = items;
|
||||||
}
|
}
|
||||||
@ -18,22 +18,13 @@ public class ExpandableGroup {
|
|||||||
return title;
|
return title;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<FollowModel> getItems(final boolean filtered) {
|
public List<User> getItems() {
|
||||||
if (!filtered) return items;
|
return items;
|
||||||
final ArrayList<FollowModel> followModels = new ArrayList<>();
|
|
||||||
for (final FollowModel followModel : items) if (followModel.isShown()) followModels.add(followModel);
|
|
||||||
return followModels;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getItemCount(final boolean filtered) {
|
public int getItemCount() {
|
||||||
if (items != null) {
|
if (items != null) {
|
||||||
final int size = items.size();
|
return items.size();
|
||||||
if (filtered) {
|
|
||||||
int finalSize = 0;
|
|
||||||
for (int i = 0; i < size; ++i) if (items.get(i).isShown()) ++finalSize;
|
|
||||||
return finalSize;
|
|
||||||
}
|
|
||||||
return size;
|
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package thoughtbot.expandableadapter;
|
package thoughtbot.expandableadapter;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
@ -15,6 +16,13 @@ public final class ExpandableList {
|
|||||||
this.expandedGroupIndexes = new boolean[groupsSize];
|
this.expandedGroupIndexes = new boolean[groupsSize];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ExpandableList(@NonNull final ArrayList<ExpandableGroup> groups,
|
||||||
|
@Nullable final boolean[] expandedGroupIndexes) {
|
||||||
|
this.groups = groups;
|
||||||
|
this.groupsSize = groups.size();
|
||||||
|
this.expandedGroupIndexes = expandedGroupIndexes;
|
||||||
|
}
|
||||||
|
|
||||||
public int getVisibleItemCount() {
|
public int getVisibleItemCount() {
|
||||||
int count = 0;
|
int count = 0;
|
||||||
for (int i = 0; i < groupsSize; i++) count = count + numberOfVisibleItemsInGroup(i);
|
for (int i = 0; i < groupsSize; i++) count = count + numberOfVisibleItemsInGroup(i);
|
||||||
@ -36,7 +44,7 @@ public final class ExpandableList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private int numberOfVisibleItemsInGroup(final int group) {
|
private int numberOfVisibleItemsInGroup(final int group) {
|
||||||
return expandedGroupIndexes[group] ? groups.get(group).getItemCount(true) + 1 : 1;
|
return expandedGroupIndexes[group] ? groups.get(group).getItemCount() + 1 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getFlattenedGroupIndex(@NonNull final ExpandableListPosition listPosition) {
|
public int getFlattenedGroupIndex(@NonNull final ExpandableListPosition listPosition) {
|
||||||
|
@ -16,6 +16,5 @@
|
|||||||
android:paddingLeft="8dp"
|
android:paddingLeft="8dp"
|
||||||
android:paddingEnd="8dp"
|
android:paddingEnd="8dp"
|
||||||
android:paddingRight="8dp"
|
android:paddingRight="8dp"
|
||||||
app:layoutManager="LinearLayoutManager"
|
|
||||||
tools:listitem="@layout/item_follow" />
|
tools:listitem="@layout/item_follow" />
|
||||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
Loading…
Reference in New Issue
Block a user