agrego ProfileListingFragment: pantalla para mostrar lista de usuarios de varios origenes

This commit is contained in:
erik-everardo 2024-04-25 00:20:32 -06:00
parent a8c262048e
commit 1540d8ec2a
5 changed files with 364 additions and 10 deletions

View File

@ -4,15 +4,16 @@ import androidx.fragment.app.Fragment
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.isolaatti.followers.ui.FollowersFragment
import com.isolaatti.followers.ui.FollowingFragment
import com.isolaatti.profile.profile_listing.ui.ProfileListingFragment
class FollowersViewPagerAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) {
class FollowersViewPagerAdapter(fragment: Fragment, private val userId: Int) : FragmentStateAdapter(fragment) {
override fun getItemCount(): Int = 2
override fun createFragment(position: Int): Fragment {
if(position == 0) {
return FollowersFragment()
return ProfileListingFragment.getInstanceForFollowers(userId)
}
return FollowingFragment()
return ProfileListingFragment.getInstanceForFollowing(userId)
}
}

View File

@ -19,7 +19,6 @@ import dagger.hilt.android.AndroidEntryPoint
class MainFollowersFragment : Fragment() {
private lateinit var binding: FragmentFollowersMainBinding
private val viewModel: FollowersViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater,
@ -33,12 +32,9 @@ class MainFollowersFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val userId = arguments?.getInt(ARGUMENT_USER_ID) ?: 0
arguments?.getInt(ARGUMENT_USER_ID)?.let {
viewModel.userId = it
}
binding.viewPagerFollowersMain.adapter = FollowersViewPagerAdapter(this)
binding.viewPagerFollowersMain.adapter = FollowersViewPagerAdapter(this, userId)
TabLayoutMediator(binding.tabLayoutFollowers, binding.viewPagerFollowersMain) { tab, position ->
when(position) {
0 -> tab.text = getText(R.string.followers)

View File

@ -5,12 +5,13 @@ import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.isolaatti.posting.posts.ui.PostLikesFragment
import com.isolaatti.posting.posts.ui.PostVersionsFragment
import com.isolaatti.profile.profile_listing.ui.ProfileListingFragment
class PostInfoViewPagerAdapter(fragmentActivity: FragmentActivity, private val postId: Long) : FragmentStateAdapter(fragmentActivity) {
override fun getItemCount(): Int = 2
override fun createFragment(position: Int): Fragment = if(position == 0) {
PostLikesFragment.getInstance(postId)
ProfileListingFragment.getInstanceForPostLikes(postId)
} else{
PostVersionsFragment.getInstance(postId)
}

View File

@ -0,0 +1,226 @@
package com.isolaatti.profile.profile_listing.presentation
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.isolaatti.common.ListUpdateEvent
import com.isolaatti.common.UpdateEvent
import com.isolaatti.followers.domain.FollowersRepository
import com.isolaatti.posting.posts.domain.PostsRepository
import com.isolaatti.profile.domain.entity.ProfileListItem
import com.isolaatti.utils.Resource
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class ProfileListingViewModel @Inject constructor(
private val followersRepository: FollowersRepository,
private val postsRepository: PostsRepository
): ViewModel() {
// one of these values must be set from fragment, depending on what action will be performed
var userId: Int = 0 // for followers and followings
var postId: Long = 0 // for "liked by"
private var usersList: List<ProfileListItem> = listOf()
private val _users: MutableLiveData<Pair<List<ProfileListItem>, UpdateEvent>> = MutableLiveData()
val users: LiveData<Pair<List<ProfileListItem>, UpdateEvent>> get() = _users
private val toRetry: MutableList<Runnable> = mutableListOf()
// runs the lists of "Runnable" one by one and clears list. After this is executed,
// caller should report as handled
fun retry() {
toRetry.forEach {
it.run()
}
toRetry.clear()
}
private fun getUsersLastId(): Int {
if(usersList.isEmpty()) {
return 0
}
return usersList.last().id
}
/**
* fetchFollowers for user previously set. userId must be set beforehand
*/
fun fetchFollowers(refresh: Boolean = false) {
if(userId <= 0) {
throw IllegalStateException("userId must not be 0. Be sure to set userId on view model before calling fetchFollowers method")
}
if(refresh) {
usersList = mutableListOf()
}
val updateListEvent = if(refresh) ListUpdateEvent.Refresh else ListUpdateEvent.ItemsAdded
viewModelScope.launch {
followersRepository.getFollowersOfUser(userId, getUsersLastId()).onEach {
when(it) {
is Resource.Success -> {
if(it.data != null) {
val prevCount = usersList.count()
usersList += it.data
_users.postValue(Pair(usersList, UpdateEvent(updateListEvent, arrayOf(prevCount))))
}
}
is Resource.Error -> {
toRetry.add {
fetchFollowers()
}
}
is Resource.Loading -> {}
}
}.flowOn(Dispatchers.IO).launchIn(this)
}
}
/**
* fetchFollowings for user previously set. userId must be set beforehand
*/
fun fetchFollowings(refresh: Boolean = false) {
if(userId <= 0) {
throw IllegalStateException("userId must not be 0. Be sure to set userId on view model before calling fetchFollowings method")
}
if(refresh) {
usersList = mutableListOf()
}
val updateListEvent = if(refresh) ListUpdateEvent.Refresh else ListUpdateEvent.ItemsAdded
viewModelScope.launch {
followersRepository.getFollowingsOfUser(userId, getUsersLastId()).onEach {
when(it) {
is Resource.Error -> {
toRetry.add {
fetchFollowings()
}
}
is Resource.Loading -> {}
is Resource.Success -> {
if(it.data != null) {
val prevCount = usersList.count()
usersList += it.data
_users.postValue(Pair(usersList, UpdateEvent(updateListEvent, arrayOf(prevCount))))
}
}
}
}.flowOn(Dispatchers.IO).launchIn(this)
}
}
/**
* fetchPostLikedBy for post previously set. postId must be set beforehand
*/
fun fetchPostLikedBy(refresh: Boolean = false) {
if(postId == 0L) {
throw IllegalStateException("postId must not be 0. Be sure to set postId on view model before calling fetchPostLikedBy method")
}
if(refresh) {
usersList = mutableListOf()
}
val updateListEvent = if(refresh) ListUpdateEvent.Refresh else ListUpdateEvent.ItemsAdded
viewModelScope.launch {
postsRepository.getUsersLikedPost(postId).onEach { resource ->
when(resource) {
is Resource.Error -> {
toRetry.add {
fetchFollowings()
}
}
is Resource.Loading -> {}
is Resource.Success -> {
if(resource.data != null) {
val prevCount = usersList.count()
usersList += resource.data
_users.postValue(Pair(usersList, UpdateEvent(updateListEvent, arrayOf(prevCount))))
}
}
}
}.flowOn(Dispatchers.IO).launchIn(this)
}
}
private fun replaceOnLists(user: ProfileListItem) {
val userIndex = usersList.indexOf(user)
if(userIndex >= 0) {
usersList = usersList.toMutableList().apply {
set(userIndex, user)
}
_users.postValue(Pair(usersList, UpdateEvent(ListUpdateEvent.ItemUpdated, arrayOf(userIndex))))
}
}
fun followUser(user: ProfileListItem) {
user.updatingFollowState = true
replaceOnLists(user)
viewModelScope.launch {
followersRepository.followUser(user.id).onEach {
when(it) {
is Resource.Error -> {
toRetry.add {
followUser(user)
}
}
is Resource.Loading -> {}
is Resource.Success -> {
user.following = true
user.updatingFollowState = false
replaceOnLists(user)
}
}
}.flowOn(Dispatchers.IO).launchIn(this)
}
}
fun unfollowUser(user: ProfileListItem) {
user.updatingFollowState = true
replaceOnLists(user)
viewModelScope.launch {
followersRepository.unfollowUser(user.id).onEach {
when(it) {
is Resource.Error -> {
toRetry.add {
unfollowUser(user)
}
}
is Resource.Loading -> {}
is Resource.Success -> {
user.following = false
user.updatingFollowState = false
replaceOnLists(user)
}
}
}.flowOn(Dispatchers.IO).launchIn(this)
}
}
}

View File

@ -0,0 +1,130 @@
package com.isolaatti.profile.profile_listing.ui
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.LinearLayoutManager
import com.isolaatti.common.UserItemCallback
import com.isolaatti.common.UserListRecyclerViewAdapter
import com.isolaatti.databinding.FragmentUserListBinding
import com.isolaatti.profile.domain.entity.ProfileListItem
import com.isolaatti.profile.profile_listing.presentation.ProfileListingViewModel
import com.isolaatti.profile.ui.ProfileActivity
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class ProfileListingFragment : Fragment(), UserItemCallback {
private val viewModel: ProfileListingViewModel by viewModels()
private lateinit var binding: FragmentUserListBinding
private var mode: Int = 0
private lateinit var adapter: UserListRecyclerViewAdapter
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentUserListBinding.inflate(inflater, container, false)
return binding.root
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mode = arguments?.getInt(ARG_MODE) ?: 0
when(mode) {
FOLLOWING, FOLLOWERS -> viewModel.userId = arguments?.getInt(ARG_USER_ID) ?: 0
POST_LIKES -> viewModel.postId = arguments?.getLong(ARG_POST_ID) ?: 0
}
loadData()
}
private fun loadData(refresh: Boolean = false) {
when(mode) {
FOLLOWING -> viewModel.fetchFollowings(refresh)
FOLLOWERS -> viewModel.fetchFollowers(refresh)
POST_LIKES -> viewModel.fetchPostLikedBy(refresh)
BROWSE_PROFILES -> {}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
adapter = UserListRecyclerViewAdapter(this)
binding.recyclerUsers.adapter = adapter
binding.recyclerUsers.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
viewModel.users.observe(viewLifecycleOwner) { (list, updateEvent) ->
adapter.updateData(list, updateEvent)
binding.swipeToRefresh.isRefreshing = false
}
binding.swipeToRefresh.setOnRefreshListener {
loadData(refresh = true)
}
}
companion object {
private const val ARG_MODE = "mode"
private const val ARG_USER_ID = "userId"
private const val ARG_POST_ID = "postId"
const val FOLLOWERS = 1
const val FOLLOWING = 2
const val POST_LIKES = 3
const val BROWSE_PROFILES = 4
fun getInstanceForFollowers(userId: Int): ProfileListingFragment {
return ProfileListingFragment().apply {
arguments = Bundle().apply {
putInt(ARG_MODE, FOLLOWERS)
putInt(ARG_USER_ID, userId)
}
}
}
fun getInstanceForFollowing(userId: Int): ProfileListingFragment {
return ProfileListingFragment().apply {
arguments = Bundle().apply {
putInt(ARG_MODE, FOLLOWING)
putInt(ARG_USER_ID, userId)
}
}
}
fun getInstanceForPostLikes(postId: Long): ProfileListingFragment {
return ProfileListingFragment().apply {
arguments = Bundle().apply {
putInt(ARG_MODE, POST_LIKES)
putLong(ARG_POST_ID, postId)
}
}
}
fun getInstanceForBrowseProfile(): ProfileListingFragment {
return ProfileListingFragment().apply {
arguments = Bundle().apply {
putInt(ARG_MODE, BROWSE_PROFILES)
}
}
}
}
override fun itemClick(userId: Int) {
ProfileActivity.startActivity(requireContext(), userId)
}
override fun followButtonClick(
user: ProfileListItem,
action: UserItemCallback.FollowButtonAction
) {
when(action) {
UserItemCallback.FollowButtonAction.Follow -> viewModel.followUser(user)
UserItemCallback.FollowButtonAction.Unfollow -> viewModel.unfollowUser(user)
}
}
}