se agrega pantalla "browse profiles" y se realiza refactorización

This commit is contained in:
erik-everardo 2024-04-28 01:09:50 -06:00
parent 50a70c6207
commit ab76e3827a
11 changed files with 131 additions and 21 deletions

View File

@ -1,6 +1,7 @@
package com.isolaatti.profile.domain.entity package com.isolaatti.profile.domain.entity
import com.isolaatti.profile.data.remote.ProfileListItemDto import com.isolaatti.profile.data.remote.ProfileListItemDto
import com.isolaatti.search.data.ProfileSearchDto
class ProfileListItem( class ProfileListItem(
val id: Int, val id: Int,
@ -15,6 +16,10 @@ class ProfileListItem(
fun fromDto(dto: ProfileListItemDto) = ProfileListItem( fun fromDto(dto: ProfileListItemDto) = ProfileListItem(
dto.id,dto.name, dto.profileImageId, dto.following dto.id,dto.name, dto.profileImageId, dto.following
) )
fun fromDto(dto: ProfileSearchDto) = ProfileListItem(
dto.id, dto.name, dto.imageId, dto.following
)
} }
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {

View File

@ -9,6 +9,7 @@ import com.isolaatti.common.UpdateEvent
import com.isolaatti.followers.domain.FollowersRepository import com.isolaatti.followers.domain.FollowersRepository
import com.isolaatti.posting.posts.domain.PostsRepository import com.isolaatti.posting.posts.domain.PostsRepository
import com.isolaatti.profile.domain.entity.ProfileListItem import com.isolaatti.profile.domain.entity.ProfileListItem
import com.isolaatti.search.domain.SearchRepository
import com.isolaatti.utils.Resource import com.isolaatti.utils.Resource
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -21,7 +22,8 @@ import javax.inject.Inject
@HiltViewModel @HiltViewModel
class ProfileListingViewModel @Inject constructor( class ProfileListingViewModel @Inject constructor(
private val followersRepository: FollowersRepository, private val followersRepository: FollowersRepository,
private val postsRepository: PostsRepository private val postsRepository: PostsRepository,
private val searchRepository: SearchRepository
): ViewModel() { ): ViewModel() {
// one of these values must be set from fragment, depending on what action will be performed // 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 userId: Int = 0 // for followers and followings
@ -145,7 +147,7 @@ class ProfileListingViewModel @Inject constructor(
when(resource) { when(resource) {
is Resource.Error -> { is Resource.Error -> {
toRetry.add { toRetry.add {
fetchFollowings() fetchPostLikedBy(refresh)
} }
} }
is Resource.Loading -> {} is Resource.Loading -> {}
@ -161,6 +163,34 @@ class ProfileListingViewModel @Inject constructor(
} }
} }
fun fetchProfiles(refresh: Boolean = false) {
if(refresh) {
usersList = mutableListOf()
}
val updateListEvent = if(refresh) ListUpdateEvent.Refresh else ListUpdateEvent.ItemsAdded
viewModelScope.launch {
searchRepository.getNewestUsers(getUsersLastId()).onEach { resource ->
when(resource) {
is Resource.Error -> {
toRetry.add {
fetchProfiles(refresh)
}
}
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) { private fun replaceOnLists(user: ProfileListItem) {
val userIndex = usersList.indexOf(user) val userIndex = usersList.indexOf(user)

View File

@ -49,7 +49,7 @@ class ProfileListingFragment : Fragment(), UserItemCallback {
FOLLOWING -> viewModel.fetchFollowings(refresh) FOLLOWING -> viewModel.fetchFollowings(refresh)
FOLLOWERS -> viewModel.fetchFollowers(refresh) FOLLOWERS -> viewModel.fetchFollowers(refresh)
POST_LIKES -> viewModel.fetchPostLikedBy(refresh) POST_LIKES -> viewModel.fetchPostLikedBy(refresh)
BROWSE_PROFILES -> {} BROWSE_PROFILES -> viewModel.fetchProfiles(refresh)
} }
} }
@ -70,7 +70,7 @@ class ProfileListingFragment : Fragment(), UserItemCallback {
} }
companion object { companion object {
private const val ARG_MODE = "mode" const val ARG_MODE = "mode"
private const val ARG_USER_ID = "userId" private const val ARG_USER_ID = "userId"
private const val ARG_POST_ID = "postId" private const val ARG_POST_ID = "postId"
const val FOLLOWERS = 1 const val FOLLOWERS = 1

View File

@ -1,6 +1,39 @@
package com.isolaatti.profile.ui package com.isolaatti.profile.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.Fragment
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.fragment.findNavController
import com.isolaatti.R
import com.isolaatti.databinding.FragmentBrowseProfileBinding
import com.isolaatti.posting.posts.ui.PostListingFragment
import com.isolaatti.profile.profile_listing.ui.ProfileListingFragment
class BrowseProfilesFragment : Fragment() { class BrowseProfilesFragment : Fragment() {
private lateinit var binding: FragmentBrowseProfileBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentBrowseProfileBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.toolbar.setNavigationOnClickListener {
findNavController().popBackStack()
}
(childFragmentManager.findFragmentById(R.id.post_list_fragment_container) as NavHostFragment)
.navController.setGraph(R.navigation.profile_listing_navigation, Bundle().apply { putInt(
ProfileListingFragment.ARG_MODE, ProfileListingFragment.BROWSE_PROFILES) })
}
} }

View File

@ -1,6 +1,7 @@
package com.isolaatti.search.data package com.isolaatti.search.data
import android.util.Log import android.util.Log
import com.isolaatti.profile.domain.entity.ProfileListItem
import com.isolaatti.search.domain.SearchRepository import com.isolaatti.search.domain.SearchRepository
import com.isolaatti.utils.Resource import com.isolaatti.utils.Resource
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -66,16 +67,16 @@ class SearchRepositoryImpl(private val searchApi: SearchApi, private val searchD
} }
} }
override fun getNewestUsers(): Flow<Resource<NewestUsersDto>> = flow { override fun getNewestUsers(after: Int?): Flow<Resource<List<ProfileListItem>>> = flow {
emit(Resource.Loading()) emit(Resource.Loading())
try { try {
val result = searchApi.getNewestUsers(10, null).awaitResponse() val result = searchApi.getNewestUsers(10, after).awaitResponse()
if(result.isSuccessful) { if(result.isSuccessful) {
val dto = result.body() val dto = result.body()
if(dto != null) { if(dto != null) {
emit(Resource.Success(dto)) emit(Resource.Success(dto.result.map { ProfileListItem.fromDto(it) }))
} }
} else { } else {

View File

@ -1,7 +1,7 @@
package com.isolaatti.search.domain package com.isolaatti.search.domain
import com.isolaatti.profile.domain.entity.ProfileListItem
import com.isolaatti.search.data.HashtagsDto import com.isolaatti.search.data.HashtagsDto
import com.isolaatti.search.data.NewestUsersDto
import com.isolaatti.search.data.SearchDto import com.isolaatti.search.data.SearchDto
import com.isolaatti.search.data.SearchHistoryEntity import com.isolaatti.search.data.SearchHistoryEntity
import com.isolaatti.utils.Resource import com.isolaatti.utils.Resource
@ -15,5 +15,5 @@ interface SearchRepository {
fun removeSuggestion(query: String): Flow<Resource<Boolean>> fun removeSuggestion(query: String): Flow<Resource<Boolean>>
fun getTrendingHashtags(): Flow<Resource<HashtagsDto>> fun getTrendingHashtags(): Flow<Resource<HashtagsDto>>
fun getNewestUsers(): Flow<Resource<NewestUsersDto>> fun getNewestUsers(after: Int?): Flow<Resource<List<ProfileListItem>>>
} }

View File

@ -4,6 +4,7 @@ import android.util.Log
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.profile.domain.entity.ProfileListItem
import com.isolaatti.search.data.HashtagsDto import com.isolaatti.search.data.HashtagsDto
import com.isolaatti.search.data.NewestUsersDto import com.isolaatti.search.data.NewestUsersDto
import com.isolaatti.search.data.SearchDto import com.isolaatti.search.data.SearchDto
@ -27,7 +28,7 @@ class SearchViewModel @Inject constructor(private val searchRepository: SearchRe
val searchSuggestions: MutableLiveData<List<SearchHistoryEntity>> = MutableLiveData() val searchSuggestions: MutableLiveData<List<SearchHistoryEntity>> = MutableLiveData()
val searchResults: MutableLiveData<SearchDto> = MutableLiveData() val searchResults: MutableLiveData<SearchDto> = MutableLiveData()
val trendingHashtags: MutableLiveData<HashtagsDto> = MutableLiveData() val trendingHashtags: MutableLiveData<HashtagsDto> = MutableLiveData()
val newestUsers: MutableLiveData<NewestUsersDto> = MutableLiveData() val newestUsers: MutableLiveData<List<ProfileListItem>> = MutableLiveData()
var searchQuery = "" var searchQuery = ""
@ -95,7 +96,7 @@ class SearchViewModel @Inject constructor(private val searchRepository: SearchRe
fun loadNewestUsers() { fun loadNewestUsers() {
viewModelScope.launch { viewModelScope.launch {
searchRepository.getNewestUsers().onEach { resource -> searchRepository.getNewestUsers(null).onEach { resource ->
when(resource) { when(resource) {
is Resource.Error -> {} is Resource.Error -> {}
is Resource.Loading -> {} is Resource.Loading -> {}

View File

@ -8,25 +8,26 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder
import coil.load import coil.load
import com.isolaatti.R import com.isolaatti.R
import com.isolaatti.databinding.UsersCarouselItemBinding import com.isolaatti.databinding.UsersCarouselItemBinding
import com.isolaatti.profile.domain.entity.ProfileListItem
import com.isolaatti.search.data.ProfileSearchDto import com.isolaatti.search.data.ProfileSearchDto
import com.isolaatti.utils.UrlGen import com.isolaatti.utils.UrlGen
class UserCarouselAdapter( class UserCarouselAdapter(
private val onProfileClick: (profileId: Int) -> Unit = {} private val onProfileClick: (profileId: Int) -> Unit = {}
) : ListAdapter<ProfileSearchDto, UserCarouselAdapter.UserCarouselItemViewHolder>(itemCallback) { ) : ListAdapter<ProfileListItem, UserCarouselAdapter.UserCarouselItemViewHolder>(itemCallback) {
companion object { companion object {
val itemCallback = object: DiffUtil.ItemCallback<ProfileSearchDto>() { val itemCallback = object: DiffUtil.ItemCallback<ProfileListItem>() {
override fun areItemsTheSame( override fun areItemsTheSame(
oldItem: ProfileSearchDto, oldItem: ProfileListItem,
newItem: ProfileSearchDto newItem: ProfileListItem
): Boolean { ): Boolean {
return oldItem.id == newItem.id return oldItem.id == newItem.id
} }
override fun areContentsTheSame( override fun areContentsTheSame(
oldItem: ProfileSearchDto, oldItem: ProfileListItem,
newItem: ProfileSearchDto newItem: ProfileListItem
): Boolean { ): Boolean {
return oldItem == newItem return oldItem == newItem
} }
@ -44,8 +45,8 @@ class UserCarouselAdapter(
val user = getItem(position) val user = getItem(position)
holder.usersCarouselItemBinding.userCarouselName.text = user.name holder.usersCarouselItemBinding.userCarouselName.text = user.name
if(user.imageId != null) { if(user.profileImageId != null) {
holder.usersCarouselItemBinding.userCarouselImageView.load(UrlGen.imageUrl(user.imageId)) holder.usersCarouselItemBinding.userCarouselImageView.load(UrlGen.imageUrl(user.profileImageId))
} else { } else {
holder.usersCarouselItemBinding.userCarouselImageView.load(R.drawable.avatar) holder.usersCarouselItemBinding.userCarouselImageView.load(R.drawable.avatar)
} }

View File

@ -19,6 +19,8 @@ import com.google.android.material.chip.Chip
import com.isolaatti.R import com.isolaatti.R
import com.isolaatti.databinding.FragmentSearchBinding import com.isolaatti.databinding.FragmentSearchBinding
import com.isolaatti.posting.posts.viewer.ui.PostViewerActivity import com.isolaatti.posting.posts.viewer.ui.PostViewerActivity
import com.isolaatti.profile.domain.entity.ProfileListItem
import com.isolaatti.profile.profile_listing.ui.ProfileListingFragment
import com.isolaatti.profile.ui.ProfileActivity import com.isolaatti.profile.ui.ProfileActivity
import com.isolaatti.search.data.HashtagsDto import com.isolaatti.search.data.HashtagsDto
import com.isolaatti.search.data.NewestUsersDto import com.isolaatti.search.data.NewestUsersDto
@ -67,8 +69,8 @@ class SearchFragment : Fragment() {
} }
} }
private val newestUsersObserver: Observer<NewestUsersDto> = Observer { private val newestUsersObserver: Observer<List<ProfileListItem>> = Observer {
newestUsersAdapter?.submitList(it.result) newestUsersAdapter?.submitList(it)
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:title="@string/browse_profiles"
app:navigationIcon="@drawable/baseline_close_24"/>
</com.google.android.material.appbar.AppBarLayout>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/post_list_fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
android:name="androidx.navigation.fragment.NavHostFragment"
app:defaultNavHost="true"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/profile_listing_navigation"
app:startDestination="@id/profileListingFragment">
<fragment
android:id="@+id/profileListingFragment"
android:name="com.isolaatti.profile.profile_listing.ui.ProfileListingFragment"
android:label="ProfileListingFragment" />
</navigation>