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
import androidx.recyclerview.widget.RecyclerView
enum class ListUpdateEvent {
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.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.Adapter
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import coil.load
import com.isolaatti.R
@ -13,18 +15,30 @@ import com.isolaatti.databinding.ItemUserListBinding
import com.isolaatti.profile.domain.entity.ProfileListItem
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)
fun updateData(newData: List<ProfileListItem>, updateEvent: UpdateEvent) {
data = newData
updateEvent.notify(this)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserListViewHolder {
return UserListViewHolder(ItemUserListBinding.inflate(LayoutInflater.from(parent.context)))
}
override fun getItemCount(): Int {
return data.size
}
override fun onBindViewHolder(holder: UserListViewHolder, position: Int) {
val context = holder.itemView.context
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 {
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.setTextColor(ResourcesCompat.getColor(context.resources, R.color.danger, null))
holder.item.followButton.setOnClickListener {
it.isEnabled = false
callback.followButtonClick(user, UserItemCallback.FollowButtonAction.Unfollow)
}
} else {
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.setOnClickListener {
it.isEnabled = false
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.ViewModel
import androidx.lifecycle.viewModelScope
import com.isolaatti.common.ListUpdateEvent
import com.isolaatti.common.UpdateEvent
import com.isolaatti.followers.domain.FollowersRepository
import com.isolaatti.profile.domain.entity.ProfileListItem
import com.isolaatti.utils.Resource
@ -19,15 +21,15 @@ import javax.inject.Inject
class FollowersViewModel @Inject constructor(private val followersRepository: FollowersRepository) : ViewModel() {
var userId: Int = 0
private val followersList: MutableList<ProfileListItem> = mutableListOf()
private val followingsList: MutableList<ProfileListItem> = mutableListOf()
private var followersList: List<ProfileListItem> = listOf()
private var followingsList: List<ProfileListItem> = listOf()
private val _followers: MutableLiveData<List<ProfileListItem>> = MutableLiveData()
private val _followings: MutableLiveData<List<ProfileListItem>> = MutableLiveData()
private val _followers: MutableLiveData<Pair<List<ProfileListItem>, UpdateEvent>> = MutableLiveData()
private val _followings: MutableLiveData<Pair<List<ProfileListItem>, UpdateEvent>> = MutableLiveData()
val followers: LiveData<List<ProfileListItem>> get() = _followers
val followings: LiveData<List<ProfileListItem>> get() = _followings
val followers: LiveData<Pair<List<ProfileListItem>, UpdateEvent>> get() = _followers
val followings: LiveData<Pair<List<ProfileListItem>, UpdateEvent>> get() = _followings
private val toRetry: MutableList<Runnable> = mutableListOf()
@ -59,20 +61,26 @@ class FollowersViewModel @Inject constructor(private val followersRepository: Fo
return followingsList.last().id
}
fun fetchFollowers() {
fun fetchFollowers(refresh: Boolean = false) {
if(userId <= 0) {
return
}
if(refresh) {
followersList = mutableListOf()
}
val updateListEvent = if(refresh) ListUpdateEvent.Refresh else ListUpdateEvent.ItemsAdded
viewModelScope.launch {
followersRepository.getFollowersOfUser(userId, getFollowersLastId()).onEach {
when(it) {
is Resource.Success -> {
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 -> {
toRetry.add {
@ -85,11 +93,17 @@ class FollowersViewModel @Inject constructor(private val followersRepository: Fo
}
}
fun fetchFollowings() {
fun fetchFollowings(refresh: Boolean = false) {
if(userId <= 0) {
return
}
if(refresh) {
followingsList = mutableListOf()
}
val updateListEvent = if(refresh) ListUpdateEvent.Refresh else ListUpdateEvent.ItemsAdded
viewModelScope.launch {
followersRepository.getFollowingsOfUser(userId, getFollowingsLastId()).onEach {
when(it) {
@ -101,8 +115,9 @@ class FollowersViewModel @Inject constructor(private val followersRepository: Fo
is Resource.Loading -> {}
is Resource.Success -> {
if(it.data != null) {
followingsList.addAll(it.data)
_followings.postValue(followingsList)
val prevCount = followersList.count()
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)
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) {
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
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -12,6 +13,7 @@ import com.isolaatti.common.UserListRecyclerViewAdapter
import com.isolaatti.databinding.FragmentFollowersBinding
import com.isolaatti.followers.presentation.FollowersViewModel
import com.isolaatti.profile.domain.entity.ProfileListItem
import com.isolaatti.profile.ui.ProfileActivity
class FollowersFragment : Fragment(), UserItemCallback {
private lateinit var binding: FragmentFollowersBinding
@ -28,8 +30,9 @@ class FollowersFragment : Fragment(), UserItemCallback {
}
private fun setObservers() {
viewModel.followers.observe(viewLifecycleOwner) {
adapter.submitList(it)
viewModel.followers.observe(viewLifecycleOwner) { (list, updateEvent) ->
adapter.updateData(list, updateEvent)
binding.swipeToRefresh.isRefreshing = false
}
}
@ -37,6 +40,10 @@ class FollowersFragment : Fragment(), UserItemCallback {
adapter = UserListRecyclerViewAdapter(this)
binding.recyclerUsers.adapter = adapter
binding.recyclerUsers.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
binding.swipeToRefresh.setOnRefreshListener {
viewModel.fetchFollowers(refresh = true)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@ -48,7 +55,7 @@ class FollowersFragment : Fragment(), UserItemCallback {
}
override fun itemClick(userId: Int) {
ProfileActivity.startActivity(requireContext(), userId)
}
override fun followButtonClick(user: ProfileListItem, action: UserItemCallback.FollowButtonAction) {

View File

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

View File

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