Browse Source
greatly fix all the follower/ing stuff
greatly fix all the follower/ing stuff
closes #1334 (assuming) closes #1340 (by implementing API search) closes #1342 closes #1472 closes #1473renovate/org.jetbrains.kotlinx-kotlinx-coroutines-test-1.x
Austin Huang
3 years ago
No known key found for this signature in database
GPG Key ID: 84C23AA04587A91F
11 changed files with 438 additions and 585 deletions
-
57app/src/main/java/awais/instagrabber/adapters/FollowAdapter.java
-
11app/src/main/java/awais/instagrabber/adapters/viewholder/FollowsViewHolder.java
-
633app/src/main/java/awais/instagrabber/fragments/FollowViewerFragment.kt
-
49app/src/main/java/awais/instagrabber/models/FollowModel.kt
-
3app/src/main/java/awais/instagrabber/repositories/FriendshipService.kt
-
19app/src/main/java/awais/instagrabber/repositories/responses/FriendshipListFetchResponse.kt
-
172app/src/main/java/awais/instagrabber/viewmodels/FollowViewModel.kt
-
45app/src/main/java/awais/instagrabber/webservices/FriendshipRepository.kt
-
23app/src/main/java/thoughtbot/expandableadapter/ExpandableGroup.java
-
10app/src/main/java/thoughtbot/expandableadapter/ExpandableList.java
-
1app/src/main/res/layout/fragment_followers_viewer.xml
@ -1,453 +1,260 @@ |
|||
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 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 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; |
|||
|
|||
public final class FollowViewerFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { |
|||
private static final String TAG = "FollowViewerFragment"; |
|||
|
|||
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); |
|||
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 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 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 |
|||
|
|||
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 |
|||
|
|||
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"; |
|||
} |
|||
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(); |
|||
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 |
|||
public void onResume() { |
|||
super.onResume(); |
|||
setTitle(username); |
|||
setSubtitle(type); |
|||
override fun 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 fun setTitle(title: String?) { |
|||
val actionBar: ActionBar = fragmentActivity.supportActionBar ?: return |
|||
actionBar.title = title |
|||
} |
|||
|
|||
private void setSubtitle(final String subtitle) { |
|||
final ActionBar actionBar = fragmentActivity.getSupportActionBar(); |
|||
if (actionBar == null) return; |
|||
actionBar.setSubtitle(subtitle); |
|||
private fun setSubtitle(subtitleRes: Int) { |
|||
val actionBar: ActionBar = fragmentActivity.supportActionBar ?: return |
|||
actionBar.setSubtitle(subtitleRes) |
|||
} |
|||
|
|||
private void setSubtitle(@SuppressWarnings("SameParameterValue") final int subtitleRes) { |
|||
final ActionBar actionBar = fragmentActivity.getSupportActionBar(); |
|||
if (actionBar == null) return; |
|||
actionBar.setSubtitle(subtitleRes); |
|||
override fun onRefresh() { |
|||
lazyLoader.resetState() |
|||
viewModel.clearProgress() |
|||
if (isCompare) listCompare() |
|||
else viewModel.fetch(isFollowersList, null) |
|||
} |
|||
|
|||
@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); |
|||
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) |
|||
} |
|||
|
|||
@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; |
|||
} |
|||
cb.onSuccess(response); |
|||
}), Dispatchers.getIO()) |
|||
); |
|||
} else { |
|||
refreshAdapter(followModels, null, null, null); |
|||
layoutManager.scrollToPosition(0); |
|||
}) |
|||
binding.rvFollow.addOnScrollListener(lazyLoader) |
|||
binding.rvFollow.layoutManager = layoutManager |
|||
viewModel.getList(isFollowersList).observe(viewLifecycleOwner) { |
|||
binding.swipeRefreshLayout.isRefreshing = false |
|||
refreshAdapter(it, null, null, null) |
|||
} |
|||
} |
|||
|
|||
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); |
|||
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) |
|||
} |
|||
} |
|||
|
|||
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 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 boolean onQueryTextChange(final String query) { |
|||
if (TextUtils.isEmpty(query)) { |
|||
searching = false; |
|||
// refreshAdapter(followModels, followingModels, followersModels, allFollowing); |
|||
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) |
|||
} |
|||
} |
|||
searching = false |
|||
return true |
|||
} |
|||
// else filter.filter(query.toLowerCase()); |
|||
if (adapter != null) { |
|||
searching = true; |
|||
adapter.getFilter().filter(query); |
|||
searching = true |
|||
if (isCompare && adapter != null) { |
|||
adapter!!.filter.filter(query) |
|||
return true |
|||
} |
|||
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 |
|||
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(); |
|||
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 { |
|||
isCompare = true; |
|||
listCompare(); |
|||
isCompare = true |
|||
listCompare() |
|||
} |
|||
return true; |
|||
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,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; |
|||
// 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) |
|||
} |
|||
} |
|||
} |
|||
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 |
|||
} |
|||
|
|||
public MutableLiveData<List<FollowModel>> getList() { |
|||
if (list == null) { |
|||
list = new MutableLiveData<>(); |
|||
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 list; |
|||
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>() |
|||
} |
|||
} |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue