mirror of
				https://github.com/KokaKiwi/BarInsta
				synced 2025-10-31 11:35:34 +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.List; | ||||
| import java.util.stream.Collectors; | ||||
| 
 | ||||
| import awais.instagrabber.R; | ||||
| import awais.instagrabber.adapters.viewholder.FollowsViewHolder; | ||||
| import awais.instagrabber.databinding.ItemFollowBinding; | ||||
| import awais.instagrabber.interfaces.OnGroupClickListener; | ||||
| import awais.instagrabber.models.FollowModel; | ||||
| import awais.instagrabber.repositories.responses.User; | ||||
| import awais.instagrabber.utils.TextUtils; | ||||
| import thoughtbot.expandableadapter.ExpandableGroup; | ||||
| import thoughtbot.expandableadapter.ExpandableList; | ||||
| @ -27,28 +28,33 @@ import thoughtbot.expandableadapter.GroupViewHolder; | ||||
| // thanks to ThoughtBot's ExpandableRecyclerViewAdapter | ||||
| //   https://github.com/thoughtbot/expandable-recycler-view | ||||
| 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() { | ||||
|         @Nullable | ||||
|         @Override | ||||
|         protected FilterResults performFiltering(final CharSequence filter) { | ||||
|             if (expandableList.groups != null) { | ||||
|                 final boolean isFilterEmpty = TextUtils.isEmpty(filter); | ||||
|                 final String query = isFilterEmpty ? null : filter.toString().toLowerCase(); | ||||
| 
 | ||||
|                 for (int x = 0; x < expandableList.groups.size(); ++x) { | ||||
|                     final ExpandableGroup expandableGroup = expandableList.groups.get(x); | ||||
|                     final List<FollowModel> items = expandableGroup.getItems(false); | ||||
|                     final int itemCount = expandableGroup.getItemCount(false); | ||||
| 
 | ||||
|                     for (int i = 0; i < itemCount; ++i) { | ||||
|                         final FollowModel followModel = items.get(i); | ||||
| 
 | ||||
|                         if (isFilterEmpty) followModel.setShown(true); | ||||
|                         else followModel.setShown(hasKey(query, followModel.getUsername(), followModel.getFullName())); | ||||
|                     } | ||||
|             final List<User> filteredItems = new ArrayList<User>(); | ||||
|             if (expandableListOriginal.groups == null || TextUtils.isEmpty(filter)) return null; | ||||
|             final String query = filter.toString().toLowerCase(); | ||||
|             final ArrayList<ExpandableGroup> groups = new ArrayList<ExpandableGroup>(); | ||||
|             for (int x = 0; x < expandableListOriginal.groups.size(); ++x) { | ||||
|                 final ExpandableGroup expandableGroup = expandableListOriginal.groups.get(x); | ||||
|                 final String title = expandableGroup.getTitle(); | ||||
|                 final List<User> items = expandableGroup.getItems(); | ||||
|                 if (items != null) { | ||||
|                     final List<User> toReturn = items.stream() | ||||
|                             .filter(u -> hasKey(query, u.getUsername(), u.getFullName())) | ||||
|                             .collect(Collectors.toList()); | ||||
|                     groups.add(new ExpandableGroup(title, toReturn)); | ||||
|                 } | ||||
|             } | ||||
|             return null; | ||||
|             final FilterResults filterResults = new FilterResults(); | ||||
|             filterResults.values = new ExpandableList(groups, expandableList.expandedGroupIndexes); | ||||
|             return filterResults; | ||||
|         } | ||||
| 
 | ||||
|         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 | ||||
|         protected void publishResults(final CharSequence constraint, final FilterResults results) { | ||||
|             if (results == null) { | ||||
|                 expandableList = expandableListOriginal; | ||||
|             } | ||||
|             else { | ||||
|                 final ExpandableList filteredList = (ExpandableList) results.values; | ||||
|                 expandableList = filteredList; | ||||
|             } | ||||
|             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) { | ||||
|         this.expandableList = new ExpandableList(groups); | ||||
|         this.expandableListOriginal = new ExpandableList(groups); | ||||
|         expandableList = this.expandableListOriginal; | ||||
|         this.onClickListener = onClickListener; | ||||
|         this.hasManyGroups = groups.size() > 1; | ||||
|     } | ||||
| @ -104,7 +115,7 @@ public final class FollowAdapter extends RecyclerView.Adapter<RecyclerView.ViewH | ||||
|             gvh.toggle(isGroupExpanded(group)); | ||||
|             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); | ||||
|     } | ||||
| 
 | ||||
| @ -124,7 +135,7 @@ public final class FollowAdapter extends RecyclerView.Adapter<RecyclerView.ViewH | ||||
| 
 | ||||
|         final int groupPos = listPosition.groupPos; | ||||
|         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]; | ||||
|         expandableList.expandedGroupIndexes[groupPos] = !isExpanded; | ||||
|  | ||||
| @ -6,7 +6,6 @@ import androidx.annotation.NonNull; | ||||
| import androidx.recyclerview.widget.RecyclerView; | ||||
| 
 | ||||
| import awais.instagrabber.databinding.ItemFollowBinding; | ||||
| import awais.instagrabber.models.FollowModel; | ||||
| import awais.instagrabber.repositories.responses.User; | ||||
| 
 | ||||
| public final class FollowsViewHolder extends RecyclerView.ViewHolder { | ||||
| @ -27,14 +26,4 @@ public final class FollowsViewHolder extends RecyclerView.ViewHolder { | ||||
|         binding.fullName.setText(model.getFullName()); | ||||
|         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.res.Resources; | ||||
| import android.os.Bundle; | ||||
| import android.util.Log; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.Menu; | ||||
| import android.view.MenuInflater; | ||||
| import android.view.MenuItem; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| import android.widget.Toast; | ||||
| import android.content.Context | ||||
| import android.content.res.Resources | ||||
| import android.os.Bundle | ||||
| import android.util.Log | ||||
| import android.view.LayoutInflater | ||||
| import android.view.Menu | ||||
| 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; | ||||
| import androidx.appcompat.app.ActionBar; | ||||
| import androidx.appcompat.app.AppCompatActivity; | ||||
| import androidx.appcompat.widget.SearchView; | ||||
| import androidx.fragment.app.Fragment; | ||||
| import androidx.navigation.fragment.NavHostFragment; | ||||
| import androidx.recyclerview.widget.LinearLayoutManager; | ||||
| import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; | ||||
| import androidx.annotation.NonNull | ||||
| import androidx.annotation.Nullable | ||||
| import androidx.appcompat.app.ActionBar | ||||
| import androidx.appcompat.app.AppCompatActivity | ||||
| import androidx.appcompat.widget.SearchView | ||||
| import androidx.fragment.app.Fragment | ||||
| import androidx.lifecycle.ViewModelProvider | ||||
| import androidx.navigation.fragment.NavHostFragment | ||||
| 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.adapters.FollowAdapter; | ||||
| import awais.instagrabber.customviews.helpers.RecyclerLazyLoader; | ||||
| import awais.instagrabber.databinding.FragmentFollowersViewerBinding; | ||||
| import awais.instagrabber.models.FollowModel; | ||||
| import awais.instagrabber.repositories.responses.FriendshipListFetchResponse; | ||||
| import awais.instagrabber.utils.AppExecutors; | ||||
| import awais.instagrabber.utils.CoroutineUtilsKt; | ||||
| import awais.instagrabber.utils.TextUtils; | ||||
| import awais.instagrabber.webservices.FriendshipRepository; | ||||
| import awais.instagrabber.webservices.ServiceCallback; | ||||
| import kotlinx.coroutines.Dispatchers; | ||||
| import thoughtbot.expandableadapter.ExpandableGroup; | ||||
| import awais.instagrabber.R | ||||
| import awais.instagrabber.adapters.FollowAdapter | ||||
| import awais.instagrabber.customviews.helpers.RecyclerLazyLoader | ||||
| import awais.instagrabber.databinding.FragmentFollowersViewerBinding | ||||
| import awais.instagrabber.models.Resource | ||||
| import awais.instagrabber.repositories.responses.User | ||||
| import awais.instagrabber.utils.AppExecutors | ||||
| import awais.instagrabber.viewmodels.FollowViewModel | ||||
| import thoughtbot.expandableadapter.ExpandableGroup | ||||
| 
 | ||||
| public final class FollowViewerFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { | ||||
|     private static final String TAG = "FollowViewerFragment"; | ||||
| class FollowViewerFragment : Fragment(), SwipeRefreshLayout.OnRefreshListener { | ||||
|     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<>(); | ||||
|     private final ArrayList<FollowModel> followingModels = new ArrayList<>(); | ||||
|     private final ArrayList<FollowModel> followersModels = new ArrayList<>(); | ||||
|     private final ArrayList<FollowModel> allFollowing = new ArrayList<>(); | ||||
| 
 | ||||
|     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 | ||||
|         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, 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); | ||||
|     override fun onCreate(savedInstanceState: Bundle?) { | ||||
|         super.onCreate(savedInstanceState) | ||||
|         fragmentActivity = activity as AppCompatActivity | ||||
|         viewModel = ViewModelProvider(this).get(FollowViewModel::class.java) | ||||
|         setHasOptionsMenu(true) | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) { | ||||
|     override fun onCreateView( | ||||
|         inflater: LayoutInflater, | ||||
|         container: ViewGroup?, | ||||
|         savedInstanceState: Bundle? | ||||
|     ): View { | ||||
|         if (root != null) { | ||||
|             shouldRefresh = false; | ||||
|             return root; | ||||
|             shouldRefresh = false | ||||
|             return root!! | ||||
|         } | ||||
|         binding = FragmentFollowersViewerBinding.inflate(getLayoutInflater()); | ||||
|         root = binding.getRoot(); | ||||
|         return root; | ||||
|         binding = FragmentFollowersViewerBinding.inflate(layoutInflater) | ||||
|         root = binding.root | ||||
|         return root!! | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { | ||||
|         if (!shouldRefresh) return; | ||||
|         init(); | ||||
|         shouldRefresh = false; | ||||
|     override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | ||||
|         if (!shouldRefresh) return | ||||
|         init() | ||||
|         shouldRefresh = false | ||||
|     } | ||||
| 
 | ||||
|     private void init() { | ||||
|         if (getArguments() == null) return; | ||||
|         final FollowViewerFragmentArgs fragmentArgs = FollowViewerFragmentArgs.fromBundle(getArguments()); | ||||
|         profileId = fragmentArgs.getProfileId(); | ||||
|         isFollowersList = fragmentArgs.getIsFollowersList(); | ||||
|         username = fragmentArgs.getUsername(); | ||||
|         namePost = username; | ||||
|         if (TextUtils.isEmpty(username)) { | ||||
|             // this usually should not occur | ||||
|             username = "You"; | ||||
|             namePost = "You're"; | ||||
|     private fun init() { | ||||
|         val args = arguments ?: return | ||||
|         val fragmentArgs = FollowViewerFragmentArgs.fromBundle(args) | ||||
|         viewModel.userId.value = fragmentArgs.profileId | ||||
|         isFollowersList = fragmentArgs.isFollowersList | ||||
|         username = fragmentArgs.username | ||||
|         namePost = username | ||||
|         setTitle(username) | ||||
|         binding.swipeRefreshLayout.setOnRefreshListener(this) | ||||
|         if (isCompare) listCompare() else listFollows() | ||||
|         viewModel.fetch(isFollowersList, null) | ||||
|     } | ||||
| 
 | ||||
|     override fun onResume() { | ||||
|         super.onResume() | ||||
|         setTitle(username) | ||||
|         setSubtitle(type) | ||||
|     } | ||||
| 
 | ||||
|     private fun setTitle(title: String?) { | ||||
|         val actionBar: ActionBar = fragmentActivity.supportActionBar ?: return | ||||
|         actionBar.title = title | ||||
|     } | ||||
| 
 | ||||
|     private fun setSubtitle(subtitleRes: Int) { | ||||
|         val actionBar: ActionBar = fragmentActivity.supportActionBar ?: return | ||||
|         actionBar.setSubtitle(subtitleRes) | ||||
|     } | ||||
| 
 | ||||
|     override fun onRefresh() { | ||||
|         lazyLoader.resetState() | ||||
|         viewModel.clearProgress() | ||||
|         if (isCompare) listCompare() | ||||
|         else viewModel.fetch(isFollowersList, null) | ||||
|     } | ||||
| 
 | ||||
|     private fun listFollows() { | ||||
|         viewModel.comparison.removeObservers(viewLifecycleOwner) | ||||
|         viewModel.status.removeObservers(viewLifecycleOwner) | ||||
|         type = if (isFollowersList) R.string.followers_type_followers else R.string.followers_type_following | ||||
|         setSubtitle(type) | ||||
|         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) | ||||
|         } | ||||
|         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); | ||||
|     } | ||||
| 
 | ||||
|     private fun listCompare() { | ||||
|         viewModel.getList(isFollowersList).removeObservers(viewLifecycleOwner) | ||||
|         binding.rvFollow.clearOnScrollListeners() | ||||
|         binding.swipeRefreshLayout.isRefreshing = true | ||||
|         setSubtitle(R.string.followers_compare) | ||||
|         viewModel.status.observe(viewLifecycleOwner) {} | ||||
|         viewModel.comparison.observe(viewLifecycleOwner) { | ||||
|             if (it != null) { | ||||
|                 binding.swipeRefreshLayout.isRefreshing = false | ||||
|                 refreshAdapter(null, it.first, it.second, it.third) | ||||
|             } | ||||
|         }; | ||||
|         binding.swipeRefreshLayout.setOnRefreshListener(this); | ||||
|         onRefresh(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onResume() { | ||||
|         super.onResume(); | ||||
|         setTitle(username); | ||||
|         setSubtitle(type); | ||||
|     } | ||||
| 
 | ||||
|     private void setTitle(final String title) { | ||||
|         final ActionBar actionBar = fragmentActivity.getSupportActionBar(); | ||||
|         if (actionBar == null) return; | ||||
|         actionBar.setTitle(title); | ||||
|     } | ||||
| 
 | ||||
|     private void setSubtitle(final String subtitle) { | ||||
|         final ActionBar actionBar = fragmentActivity.getSupportActionBar(); | ||||
|         if (actionBar == null) return; | ||||
|         actionBar.setSubtitle(subtitle); | ||||
|     } | ||||
| 
 | ||||
|     private void setSubtitle(@SuppressWarnings("SameParameterValue") final int subtitleRes) { | ||||
|         final ActionBar actionBar = fragmentActivity.getSupportActionBar(); | ||||
|         if (actionBar == null) return; | ||||
|         actionBar.setSubtitle(subtitleRes); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onRefresh() { | ||||
|         if (isCompare) listCompare(); | ||||
|         else listFollows(); | ||||
|         endCursor = null; | ||||
|         lazyLoader.resetState(); | ||||
|     } | ||||
| 
 | ||||
|     private void listFollows() { | ||||
|         type = resources.getString(isFollowersList ? R.string.followers_type_followers : R.string.followers_type_following); | ||||
|         setSubtitle(type); | ||||
|         final ServiceCallback<FriendshipListFetchResponse> cb = new ServiceCallback<FriendshipListFetchResponse>() { | ||||
|             @Override | ||||
|             public void onSuccess(final FriendshipListFetchResponse result) { | ||||
|                 if (result == null) { | ||||
|                     binding.swipeRefreshLayout.setRefreshing(false); | ||||
|                     return; | ||||
|                 } | ||||
|                 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 fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { | ||||
|         inflater.inflate(R.menu.follow, menu) | ||||
|         val menuSearch = menu.findItem(R.id.action_search) | ||||
|         val searchView = menuSearch.actionView as SearchView | ||||
|         searchView.queryHint = resources.getString(R.string.action_search) | ||||
|         searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { | ||||
|             override fun onQueryTextSubmit(query: String): Boolean { | ||||
|                 return 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 (single)", t); | ||||
|             } | ||||
|         }; | ||||
|         layoutManager = new LinearLayoutManager(getContext()); | ||||
|         lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> { | ||||
|             if (!TextUtils.isEmpty(endCursor) && !searching) { | ||||
|                 binding.swipeRefreshLayout.setRefreshing(true); | ||||
|                 layoutManager.setStackFromEnd(true); | ||||
|                 friendshipRepository.getList( | ||||
|                         isFollowersList, | ||||
|                         profileId, | ||||
|                         endCursor, | ||||
|                         CoroutineUtilsKt.getContinuation((response, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> { | ||||
|                             if (throwable != null) { | ||||
|                                 cb.onFailure(throwable); | ||||
|                                 return; | ||||
|                             } | ||||
|                             cb.onSuccess(response); | ||||
|                         }), Dispatchers.getIO()) | ||||
|                 ); | ||||
|                 endCursor = null; | ||||
|             } | ||||
|         }); | ||||
|         binding.rvFollow.addOnScrollListener(lazyLoader); | ||||
|         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; | ||||
|             override fun onQueryTextChange(query: String): Boolean { | ||||
|                 if (query.isNullOrEmpty()) { | ||||
|                     if (!isCompare && searching) { | ||||
|                         viewModel.setQuery(null, isFollowersList) | ||||
|                         viewModel.getSearch().removeObservers(viewLifecycleOwner) | ||||
|                         viewModel.getList(isFollowersList).observe(viewLifecycleOwner) { | ||||
|                             refreshAdapter(it, null, null, null) | ||||
|                         } | ||||
|                         cb.onSuccess(response); | ||||
|                     }), Dispatchers.getIO()) | ||||
|             ); | ||||
|                     } | ||||
|                     searching = false | ||||
|                     return true | ||||
|                 } | ||||
|                 searching = true | ||||
|                 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 { | ||||
|             refreshAdapter(followModels, null, null, null); | ||||
|             layoutManager.scrollToPosition(0); | ||||
|             isCompare = true | ||||
|             listCompare() | ||||
|         } | ||||
|         return true | ||||
|     } | ||||
| 
 | ||||
|     private void listCompare() { | ||||
|         layoutManager.setStackFromEnd(false); | ||||
|         binding.rvFollow.clearOnScrollListeners(); | ||||
|         loading = true; | ||||
|         setSubtitle(R.string.followers_compare); | ||||
|         allFollowing.clear(); | ||||
|         if (moreAvailable) { | ||||
|             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); | ||||
| 
 | ||||
|     private fun refreshAdapter( | ||||
|         followModels: List<User>?, | ||||
|         allFollowing: List<User>?, | ||||
|         followingModels: List<User>?, | ||||
|         followersModels: List<User>? | ||||
|     ) { | ||||
|         val groups: ArrayList<ExpandableGroup> = ArrayList<ExpandableGroup>(1) | ||||
|         if (isCompare && followingModels != null && followersModels != null && allFollowing != null) { | ||||
|             if (followingModels.size() > 0) | ||||
|                 groups.add(new ExpandableGroup(resources.getString(R.string.followers_not_following, username), followingModels)); | ||||
|             if (followersModels.size() > 0) | ||||
|                 groups.add(new ExpandableGroup(resources.getString(R.string.followers_not_follower, namePost), followersModels)); | ||||
|             if (allFollowing.size() > 0) | ||||
|                 groups.add(new ExpandableGroup(resources.getString(R.string.followers_both_following), allFollowing)); | ||||
|             if (followingModels.size > 0) groups.add( | ||||
|                 ExpandableGroup( | ||||
|                     getString( | ||||
|                         R.string.followers_not_following, | ||||
|                         username | ||||
|                     ), 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) { | ||||
|             groups.add(new ExpandableGroup(type, followModels)); | ||||
|         } else return; | ||||
|         adapter = new FollowAdapter(clickListener, groups); | ||||
|         adapter.toggleGroup(0); | ||||
|         binding.rvFollow.setAdapter(adapter); | ||||
|             groups.add(ExpandableGroup(getString(type), followModels)) | ||||
|         } else return | ||||
|         adapter = FollowAdapter({ v -> | ||||
|             val tag = v.tag | ||||
|             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 | ||||
| 
 | ||||
| import awais.instagrabber.repositories.responses.FriendshipChangeResponse | ||||
| import awais.instagrabber.repositories.responses.FriendshipListFetchResponse | ||||
| import awais.instagrabber.repositories.responses.FriendshipRestrictResponse | ||||
| import retrofit2.http.* | ||||
| 
 | ||||
| @ -25,7 +26,7 @@ interface FriendshipService { | ||||
|         @Path("userId") userId: Long, | ||||
|         @Path("type") type: String,  // following or followers | ||||
|         @QueryMap(encoded = true) queryParams: Map<String, String>, | ||||
|     ): String | ||||
|     ): FriendshipListFetchResponse | ||||
| 
 | ||||
|     @FormUrlEncoded | ||||
|     @POST("/api/v1/friendships/{action}/") | ||||
|  | ||||
| @ -1,27 +1,10 @@ | ||||
| package awais.instagrabber.repositories.responses | ||||
| 
 | ||||
| import awais.instagrabber.models.FollowModel | ||||
| 
 | ||||
| data class FriendshipListFetchResponse( | ||||
|     var nextMaxId: String?, | ||||
|     var status: String?, | ||||
|     var items: List<FollowModel>? | ||||
|     var users: List<User>? | ||||
| ) { | ||||
|     val isMoreAvailable: Boolean | ||||
|         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.ViewModel; | ||||
| import androidx.lifecycle.LiveData | ||||
| 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 { | ||||
|     private MutableLiveData<List<FollowModel>> list; | ||||
| 
 | ||||
|     public MutableLiveData<List<FollowModel>> getList() { | ||||
|         if (list == null) { | ||||
|             list = new MutableLiveData<>(); | ||||
|     // comparison | ||||
|     val status: LiveData<Pair<Boolean, Boolean>> = object : MediatorLiveData<Pair<Boolean, Boolean>>() { | ||||
|             init { | ||||
|                 postValue(Pair(false, false)) | ||||
|                 addSource(followersMaxId) { | ||||
|                     if (it == null) { | ||||
|                         postValue(Pair(true, value!!.second)) | ||||
|                     } | ||||
|                     else fetch(true, it) | ||||
|                 } | ||||
|                 addSource(followingMaxId) { | ||||
|                     if (it == null) { | ||||
|                         postValue(Pair(value!!.first, true)) | ||||
|                     } | ||||
|                     else fetch(false, it) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return list; | ||||
|     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 | ||||
| 
 | ||||
| import awais.instagrabber.models.FollowModel | ||||
| import awais.instagrabber.repositories.FriendshipService | ||||
| import awais.instagrabber.repositories.responses.FriendshipChangeResponse | ||||
| import awais.instagrabber.repositories.responses.FriendshipListFetchResponse | ||||
| import awais.instagrabber.repositories.responses.FriendshipRestrictResponse | ||||
| import awais.instagrabber.utils.Utils | ||||
| import awais.instagrabber.webservices.RetrofitFactory.retrofit | ||||
| import org.json.JSONArray | ||||
| import org.json.JSONException | ||||
| import org.json.JSONObject | ||||
| 
 | ||||
| class FriendshipRepository(private val service: FriendshipService) { | ||||
| 
 | ||||
| @ -113,43 +109,12 @@ class FriendshipRepository(private val service: FriendshipService) { | ||||
|         follower: Boolean, | ||||
|         targetUserId: Long, | ||||
|         maxId: String?, | ||||
|         query: String? | ||||
|     ): FriendshipListFetchResponse { | ||||
|         val queryMap = if (maxId != null) mapOf("max_id" to maxId) else emptyMap() | ||||
|         val response = service.getList(targetUserId, if (follower) "followers" else "following", queryMap) | ||||
|         return parseListResponse(response) | ||||
|     } | ||||
| 
 | ||||
|     @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 | ||||
|         val queryMap: MutableMap<String, String> = mutableMapOf() | ||||
|         if (!maxId.isNullOrEmpty()) queryMap.set("max_id", maxId) | ||||
|         if (!query.isNullOrEmpty()) queryMap.set("query", query) | ||||
|         return service.getList(targetUserId, if (follower) "followers" else "following", queryMap.toMap()) | ||||
|     } | ||||
| 
 | ||||
|     companion object { | ||||
|  | ||||
| @ -3,13 +3,13 @@ package thoughtbot.expandableadapter; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import awais.instagrabber.models.FollowModel; | ||||
| import awais.instagrabber.repositories.responses.User; | ||||
| 
 | ||||
| public class ExpandableGroup { | ||||
|     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.items = items; | ||||
|     } | ||||
| @ -18,22 +18,13 @@ public class ExpandableGroup { | ||||
|         return title; | ||||
|     } | ||||
| 
 | ||||
|     public List<FollowModel> getItems(final boolean filtered) { | ||||
|         if (!filtered) return items; | ||||
|         final ArrayList<FollowModel> followModels = new ArrayList<>(); | ||||
|         for (final FollowModel followModel : items) if (followModel.isShown()) followModels.add(followModel); | ||||
|         return followModels; | ||||
|     public List<User> getItems() { | ||||
|         return items; | ||||
|     } | ||||
| 
 | ||||
|     public int getItemCount(final boolean filtered) { | ||||
|     public int getItemCount() { | ||||
|         if (items != null) { | ||||
|             final int size = 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 items.size(); | ||||
|         } | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| package thoughtbot.expandableadapter; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| 
 | ||||
| @ -15,6 +16,13 @@ public final class ExpandableList { | ||||
|         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() { | ||||
|         int count = 0; | ||||
|         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) { | ||||
|         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) { | ||||
|  | ||||
| @ -16,6 +16,5 @@ | ||||
|         android:paddingLeft="8dp" | ||||
|         android:paddingEnd="8dp" | ||||
|         android:paddingRight="8dp" | ||||
|         app:layoutManager="LinearLayoutManager" | ||||
|         tools:listitem="@layout/item_follow" /> | ||||
| </androidx.swiperefreshlayout.widget.SwipeRefreshLayout> | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user