agrego ProfileListingFragment: pantalla para mostrar lista de usuarios de varios origenes
This commit is contained in:
parent
a8c262048e
commit
1540d8ec2a
@ -4,15 +4,16 @@ import androidx.fragment.app.Fragment
|
|||||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||||
import com.isolaatti.followers.ui.FollowersFragment
|
import com.isolaatti.followers.ui.FollowersFragment
|
||||||
import com.isolaatti.followers.ui.FollowingFragment
|
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 getItemCount(): Int = 2
|
||||||
|
|
||||||
override fun createFragment(position: Int): Fragment {
|
override fun createFragment(position: Int): Fragment {
|
||||||
if(position == 0) {
|
if(position == 0) {
|
||||||
return FollowersFragment()
|
return ProfileListingFragment.getInstanceForFollowers(userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
return FollowingFragment()
|
return ProfileListingFragment.getInstanceForFollowing(userId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -19,7 +19,6 @@ import dagger.hilt.android.AndroidEntryPoint
|
|||||||
class MainFollowersFragment : Fragment() {
|
class MainFollowersFragment : Fragment() {
|
||||||
|
|
||||||
private lateinit var binding: FragmentFollowersMainBinding
|
private lateinit var binding: FragmentFollowersMainBinding
|
||||||
private val viewModel: FollowersViewModel by viewModels()
|
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
@ -33,12 +32,9 @@ class MainFollowersFragment : Fragment() {
|
|||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
val userId = arguments?.getInt(ARGUMENT_USER_ID) ?: 0
|
||||||
|
|
||||||
arguments?.getInt(ARGUMENT_USER_ID)?.let {
|
binding.viewPagerFollowersMain.adapter = FollowersViewPagerAdapter(this, userId)
|
||||||
viewModel.userId = it
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.viewPagerFollowersMain.adapter = FollowersViewPagerAdapter(this)
|
|
||||||
TabLayoutMediator(binding.tabLayoutFollowers, binding.viewPagerFollowersMain) { tab, position ->
|
TabLayoutMediator(binding.tabLayoutFollowers, binding.viewPagerFollowersMain) { tab, position ->
|
||||||
when(position) {
|
when(position) {
|
||||||
0 -> tab.text = getText(R.string.followers)
|
0 -> tab.text = getText(R.string.followers)
|
||||||
|
|||||||
@ -5,12 +5,13 @@ import androidx.fragment.app.FragmentActivity
|
|||||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||||
import com.isolaatti.posting.posts.ui.PostLikesFragment
|
import com.isolaatti.posting.posts.ui.PostLikesFragment
|
||||||
import com.isolaatti.posting.posts.ui.PostVersionsFragment
|
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) {
|
class PostInfoViewPagerAdapter(fragmentActivity: FragmentActivity, private val postId: Long) : FragmentStateAdapter(fragmentActivity) {
|
||||||
override fun getItemCount(): Int = 2
|
override fun getItemCount(): Int = 2
|
||||||
|
|
||||||
override fun createFragment(position: Int): Fragment = if(position == 0) {
|
override fun createFragment(position: Int): Fragment = if(position == 0) {
|
||||||
PostLikesFragment.getInstance(postId)
|
ProfileListingFragment.getInstanceForPostLikes(postId)
|
||||||
} else{
|
} else{
|
||||||
PostVersionsFragment.getInstance(postId)
|
PostVersionsFragment.getInstance(postId)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user