lista de seguidores y seguidos

This commit is contained in:
erik-everardo 2024-01-07 19:41:28 -06:00
parent 36c25a37d6
commit 75b93da044
6 changed files with 112 additions and 47 deletions

View File

@ -1,6 +1,42 @@
package com.isolaatti.common package com.isolaatti.common
import androidx.recyclerview.widget.RecyclerView
enum class ListUpdateEvent { enum class ListUpdateEvent {
ItemUpdated, ItemUpdated,
ItemsAdded ItemsAdded,
Refresh
}
/**
* @param listUpdateEvent indicate what type of update was made to the list
* @param items list of positions. For ItemUpdated these positions indicate the changed positions.
* For ItemsAdded indicate the range where the item was inserted, first element the beginning, second element the end.
* If list contains only one element, only that position will be inserted
*/
class UpdateEvent(private val listUpdateEvent: ListUpdateEvent, private val items: Array<Int>) {
fun notify(adapter: RecyclerView.Adapter<*>) {
when(listUpdateEvent) {
ListUpdateEvent.ItemUpdated -> {
items.forEach { position ->
adapter.notifyItemChanged(position)
}
}
ListUpdateEvent.ItemsAdded -> {
if(items.isEmpty()) {
return
}
if(items.count() == 1) {
adapter.notifyItemInserted(items[0])
} else {
adapter.notifyItemRangeInserted(items[0], items[1])
}
}
ListUpdateEvent.Refresh -> {
adapter.notifyDataSetChanged()
}
}
}
} }

View File

@ -5,6 +5,8 @@ import android.view.ViewGroup
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.Adapter
import androidx.recyclerview.widget.RecyclerView.ViewHolder import androidx.recyclerview.widget.RecyclerView.ViewHolder
import coil.load import coil.load
import com.isolaatti.R import com.isolaatti.R
@ -13,18 +15,30 @@ import com.isolaatti.databinding.ItemUserListBinding
import com.isolaatti.profile.domain.entity.ProfileListItem import com.isolaatti.profile.domain.entity.ProfileListItem
import com.isolaatti.utils.UrlGen import com.isolaatti.utils.UrlGen
class UserListRecyclerViewAdapter(private val callback: UserItemCallback) : ListAdapter<ProfileListItem, UserListRecyclerViewAdapter.UserListViewHolder>(diffCallback) { class UserListRecyclerViewAdapter(private val callback: UserItemCallback) : Adapter<UserListRecyclerViewAdapter.UserListViewHolder>() {
private var data: List<ProfileListItem> = listOf()
inner class UserListViewHolder(val item: ItemUserListBinding) : ViewHolder(item.root) inner class UserListViewHolder(val item: ItemUserListBinding) : ViewHolder(item.root)
fun updateData(newData: List<ProfileListItem>, updateEvent: UpdateEvent) {
data = newData
updateEvent.notify(this)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserListViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserListViewHolder {
return UserListViewHolder(ItemUserListBinding.inflate(LayoutInflater.from(parent.context))) return UserListViewHolder(ItemUserListBinding.inflate(LayoutInflater.from(parent.context)))
} }
override fun getItemCount(): Int {
return data.size
}
override fun onBindViewHolder(holder: UserListViewHolder, position: Int) { override fun onBindViewHolder(holder: UserListViewHolder, position: Int) {
val context = holder.itemView.context val context = holder.itemView.context
holder.itemView.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT) holder.itemView.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT)
getItem(position).let { user -> holder.item.followButton.isEnabled = true
data[position].let { user ->
holder.item.root.setOnClickListener { holder.item.root.setOnClickListener {
callback.itemClick(user.id) callback.itemClick(user.id)
} }
@ -34,34 +48,17 @@ class UserListRecyclerViewAdapter(private val callback: UserItemCallback) : List
holder.item.followButton.text = context.getText(R.string.unfollow) holder.item.followButton.text = context.getText(R.string.unfollow)
holder.item.followButton.setTextColor(ResourcesCompat.getColor(context.resources, R.color.danger, null)) holder.item.followButton.setTextColor(ResourcesCompat.getColor(context.resources, R.color.danger, null))
holder.item.followButton.setOnClickListener { holder.item.followButton.setOnClickListener {
it.isEnabled = false
callback.followButtonClick(user, UserItemCallback.FollowButtonAction.Unfollow) callback.followButtonClick(user, UserItemCallback.FollowButtonAction.Unfollow)
} }
} else { } else {
holder.item.followButton.text = context.getText(R.string.follow) holder.item.followButton.text = context.getText(R.string.follow)
holder.item.followButton.setTextColor(ResourcesCompat.getColor(context.resources, R.color.purple_lighter, null)) holder.item.followButton.setTextColor(ResourcesCompat.getColor(context.resources, R.color.purple_lighter, null))
holder.item.followButton.setOnClickListener { holder.item.followButton.setOnClickListener {
it.isEnabled = false
callback.followButtonClick(user, UserItemCallback.FollowButtonAction.Follow) callback.followButtonClick(user, UserItemCallback.FollowButtonAction.Follow)
} }
} }
} }
} }
companion object {
val diffCallback: DiffUtil.ItemCallback<ProfileListItem> = object: DiffUtil.ItemCallback<ProfileListItem>() {
override fun areItemsTheSame(
oldItem: ProfileListItem,
newItem: ProfileListItem
): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(
oldItem: ProfileListItem,
newItem: ProfileListItem
): Boolean {
return oldItem == newItem
}
}
}
} }

View File

@ -4,6 +4,8 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.isolaatti.common.ListUpdateEvent
import com.isolaatti.common.UpdateEvent
import com.isolaatti.followers.domain.FollowersRepository import com.isolaatti.followers.domain.FollowersRepository
import com.isolaatti.profile.domain.entity.ProfileListItem import com.isolaatti.profile.domain.entity.ProfileListItem
import com.isolaatti.utils.Resource import com.isolaatti.utils.Resource
@ -19,15 +21,15 @@ import javax.inject.Inject
class FollowersViewModel @Inject constructor(private val followersRepository: FollowersRepository) : ViewModel() { class FollowersViewModel @Inject constructor(private val followersRepository: FollowersRepository) : ViewModel() {
var userId: Int = 0 var userId: Int = 0
private val followersList: MutableList<ProfileListItem> = mutableListOf() private var followersList: List<ProfileListItem> = listOf()
private val followingsList: MutableList<ProfileListItem> = mutableListOf() private var followingsList: List<ProfileListItem> = listOf()
private val _followers: MutableLiveData<List<ProfileListItem>> = MutableLiveData() private val _followers: MutableLiveData<Pair<List<ProfileListItem>, UpdateEvent>> = MutableLiveData()
private val _followings: MutableLiveData<List<ProfileListItem>> = MutableLiveData() private val _followings: MutableLiveData<Pair<List<ProfileListItem>, UpdateEvent>> = MutableLiveData()
val followers: LiveData<List<ProfileListItem>> get() = _followers val followers: LiveData<Pair<List<ProfileListItem>, UpdateEvent>> get() = _followers
val followings: LiveData<List<ProfileListItem>> get() = _followings val followings: LiveData<Pair<List<ProfileListItem>, UpdateEvent>> get() = _followings
private val toRetry: MutableList<Runnable> = mutableListOf() private val toRetry: MutableList<Runnable> = mutableListOf()
@ -59,20 +61,26 @@ class FollowersViewModel @Inject constructor(private val followersRepository: Fo
return followingsList.last().id return followingsList.last().id
} }
fun fetchFollowers() { fun fetchFollowers(refresh: Boolean = false) {
if(userId <= 0) { if(userId <= 0) {
return return
} }
if(refresh) {
followersList = mutableListOf()
}
val updateListEvent = if(refresh) ListUpdateEvent.Refresh else ListUpdateEvent.ItemsAdded
viewModelScope.launch { viewModelScope.launch {
followersRepository.getFollowersOfUser(userId, getFollowersLastId()).onEach { followersRepository.getFollowersOfUser(userId, getFollowersLastId()).onEach {
when(it) { when(it) {
is Resource.Success -> { is Resource.Success -> {
if(it.data != null) { if(it.data != null) {
followersList.addAll(it.data) val prevCount = followersList.count()
followersList += it.data
_followers.postValue(Pair(followersList, UpdateEvent(updateListEvent, arrayOf(prevCount))))
} }
_followers.postValue(followersList)
} }
is Resource.Error -> { is Resource.Error -> {
toRetry.add { toRetry.add {
@ -85,11 +93,17 @@ class FollowersViewModel @Inject constructor(private val followersRepository: Fo
} }
} }
fun fetchFollowings() { fun fetchFollowings(refresh: Boolean = false) {
if(userId <= 0) { if(userId <= 0) {
return return
} }
if(refresh) {
followingsList = mutableListOf()
}
val updateListEvent = if(refresh) ListUpdateEvent.Refresh else ListUpdateEvent.ItemsAdded
viewModelScope.launch { viewModelScope.launch {
followersRepository.getFollowingsOfUser(userId, getFollowingsLastId()).onEach { followersRepository.getFollowingsOfUser(userId, getFollowingsLastId()).onEach {
when(it) { when(it) {
@ -101,8 +115,9 @@ class FollowersViewModel @Inject constructor(private val followersRepository: Fo
is Resource.Loading -> {} is Resource.Loading -> {}
is Resource.Success -> { is Resource.Success -> {
if(it.data != null) { if(it.data != null) {
followingsList.addAll(it.data) val prevCount = followersList.count()
_followings.postValue(followingsList) followingsList += it.data
_followings.postValue(Pair(followingsList, UpdateEvent(updateListEvent, arrayOf(prevCount))))
} }
} }
} }
@ -115,15 +130,19 @@ class FollowersViewModel @Inject constructor(private val followersRepository: Fo
val followingsIndex = followingsList.indexOf(user) val followingsIndex = followingsList.indexOf(user)
if(followersIndex >= 0) { if(followersIndex >= 0) {
followersList[followersIndex] = user followersList = followersList.toMutableList().apply {
set(followersIndex, user)
}
_followers.postValue(followersList) _followers.postValue(Pair(followersList, UpdateEvent(ListUpdateEvent.ItemUpdated, arrayOf(followersIndex))))
} }
if(followingsIndex >= 0) { if(followingsIndex >= 0) {
followingsList[followingsIndex] = user followingsList = followingsList.toMutableList().apply {
set(followingsIndex, user)
}
_followings.postValue(followingsList) _followings.postValue(Pair(followingsList, UpdateEvent(ListUpdateEvent.ItemUpdated, arrayOf(followingsIndex))))
} }
} }

View File

@ -1,6 +1,7 @@
package com.isolaatti.followers.ui package com.isolaatti.followers.ui
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -12,6 +13,7 @@ import com.isolaatti.common.UserListRecyclerViewAdapter
import com.isolaatti.databinding.FragmentFollowersBinding import com.isolaatti.databinding.FragmentFollowersBinding
import com.isolaatti.followers.presentation.FollowersViewModel import com.isolaatti.followers.presentation.FollowersViewModel
import com.isolaatti.profile.domain.entity.ProfileListItem import com.isolaatti.profile.domain.entity.ProfileListItem
import com.isolaatti.profile.ui.ProfileActivity
class FollowersFragment : Fragment(), UserItemCallback { class FollowersFragment : Fragment(), UserItemCallback {
private lateinit var binding: FragmentFollowersBinding private lateinit var binding: FragmentFollowersBinding
@ -28,8 +30,9 @@ class FollowersFragment : Fragment(), UserItemCallback {
} }
private fun setObservers() { private fun setObservers() {
viewModel.followers.observe(viewLifecycleOwner) { viewModel.followers.observe(viewLifecycleOwner) { (list, updateEvent) ->
adapter.submitList(it) adapter.updateData(list, updateEvent)
binding.swipeToRefresh.isRefreshing = false
} }
} }
@ -37,6 +40,10 @@ class FollowersFragment : Fragment(), UserItemCallback {
adapter = UserListRecyclerViewAdapter(this) adapter = UserListRecyclerViewAdapter(this)
binding.recyclerUsers.adapter = adapter binding.recyclerUsers.adapter = adapter
binding.recyclerUsers.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false) binding.recyclerUsers.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
binding.swipeToRefresh.setOnRefreshListener {
viewModel.fetchFollowers(refresh = true)
}
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
@ -48,7 +55,7 @@ class FollowersFragment : Fragment(), UserItemCallback {
} }
override fun itemClick(userId: Int) { override fun itemClick(userId: Int) {
ProfileActivity.startActivity(requireContext(), userId)
} }
override fun followButtonClick(user: ProfileListItem, action: UserItemCallback.FollowButtonAction) { override fun followButtonClick(user: ProfileListItem, action: UserItemCallback.FollowButtonAction) {

View File

@ -1,6 +1,7 @@
package com.isolaatti.followers.ui package com.isolaatti.followers.ui
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -19,6 +20,8 @@ class FollowingFragment : Fragment(), UserItemCallback {
private val viewModel: FollowersViewModel by viewModels({ requireParentFragment() }) private val viewModel: FollowersViewModel by viewModels({ requireParentFragment() })
private lateinit var adapter: UserListRecyclerViewAdapter private lateinit var adapter: UserListRecyclerViewAdapter
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
@ -29,8 +32,9 @@ class FollowingFragment : Fragment(), UserItemCallback {
} }
private fun setObservers() { private fun setObservers() {
viewModel.followings.observe(viewLifecycleOwner) { viewModel.followings.observe(viewLifecycleOwner) { (list, updateEvent) ->
adapter.submitList(it) adapter.updateData(list, updateEvent)
binding.swipeToRefresh.isRefreshing = false
} }
} }
@ -38,6 +42,10 @@ class FollowingFragment : Fragment(), UserItemCallback {
adapter = UserListRecyclerViewAdapter(this) adapter = UserListRecyclerViewAdapter(this)
binding.recyclerUsers.adapter = adapter binding.recyclerUsers.adapter = adapter
binding.recyclerUsers.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false) binding.recyclerUsers.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
binding.swipeToRefresh.setOnRefreshListener {
viewModel.fetchFollowings(refresh = true)
}
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)

View File

@ -26,9 +26,7 @@ class ProfileListItem(
if (id != other.id) return false if (id != other.id) return false
if (name != other.name) return false if (name != other.name) return false
if (profileImageId != other.profileImageId) return false if (profileImageId != other.profileImageId) return false
if (following != other.following) return false return following == other.following
return true
} }
override fun hashCode(): Int { override fun hashCode(): Int {