diff --git a/app/src/main/java/com/isolaatti/common/ListUpdateEvent.kt b/app/src/main/java/com/isolaatti/common/ListUpdateEvent.kt new file mode 100644 index 0000000..209fac4 --- /dev/null +++ b/app/src/main/java/com/isolaatti/common/ListUpdateEvent.kt @@ -0,0 +1,6 @@ +package com.isolaatti.common + +enum class ListUpdateEvent { + ItemUpdated, + ItemsAdded +} \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/common/UserItemCallback.kt b/app/src/main/java/com/isolaatti/common/UserItemCallback.kt new file mode 100644 index 0000000..99015c1 --- /dev/null +++ b/app/src/main/java/com/isolaatti/common/UserItemCallback.kt @@ -0,0 +1,12 @@ +package com.isolaatti.common + +import com.isolaatti.profile.domain.entity.ProfileListItem + +interface UserItemCallback { + enum class FollowButtonAction { + Follow, Unfollow + } + fun itemClick(userId: Int) + + fun followButtonClick(user: ProfileListItem, action: FollowButtonAction) +} \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/common/UserListRecyclerViewAdapter.kt b/app/src/main/java/com/isolaatti/common/UserListRecyclerViewAdapter.kt new file mode 100644 index 0000000..902de95 --- /dev/null +++ b/app/src/main/java/com/isolaatti/common/UserListRecyclerViewAdapter.kt @@ -0,0 +1,66 @@ +package com.isolaatti.common + +import android.view.LayoutInflater +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.ViewHolder +import com.isolaatti.R +import com.isolaatti.databinding.ItemUserListBinding +import com.isolaatti.profile.domain.entity.ProfileListItem +import com.isolaatti.utils.UrlGen +import com.squareup.picasso.Picasso + +class UserListRecyclerViewAdapter(private val callback: UserItemCallback) : ListAdapter(diffCallback) { + + inner class UserListViewHolder(val item: ItemUserListBinding) : ViewHolder(item.root) + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserListViewHolder { + return UserListViewHolder(ItemUserListBinding.inflate(LayoutInflater.from(parent.context))) + } + + 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.root.setOnClickListener { + callback.itemClick(user.id) + } + holder.item.name.text = user.name + Picasso.get().load(UrlGen.userProfileImage(user.id)).into(holder.item.image) + if(user.following == true) { + 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 { + 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 { + callback.followButtonClick(user, UserItemCallback.FollowButtonAction.Follow) + } + } + } + } + + companion object { + val diffCallback: DiffUtil.ItemCallback = object: DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: ProfileListItem, + newItem: ProfileListItem + ): Boolean { + return oldItem.id == newItem.id + } + + override fun areContentsTheSame( + oldItem: ProfileListItem, + newItem: ProfileListItem + ): Boolean { + return oldItem == newItem + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/followers/data/FollowersRepositoryImpl.kt b/app/src/main/java/com/isolaatti/followers/data/FollowersRepositoryImpl.kt index adbf523..7c1ff9a 100644 --- a/app/src/main/java/com/isolaatti/followers/data/FollowersRepositoryImpl.kt +++ b/app/src/main/java/com/isolaatti/followers/data/FollowersRepositoryImpl.kt @@ -3,31 +3,43 @@ package com.isolaatti.followers.data import com.isolaatti.followers.data.remote.FollowersApi import com.isolaatti.followers.domain.FollowersRepository import com.isolaatti.profile.data.remote.ProfileListItemDto +import com.isolaatti.profile.domain.entity.ProfileListItem +import com.isolaatti.utils.IntIdentificationWrapper import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import retrofit2.awaitResponse import javax.inject.Inject class FollowersRepositoryImpl @Inject constructor(private val followersApi: FollowersApi) : FollowersRepository { - override fun getFollowersOfUser(userId: Int): Flow> = flow { - val response = followersApi.getFollowersOfUser(userId).awaitResponse() + override fun getFollowersOfUser(userId: Int, after: Int): Flow> = flow { + val response = followersApi.getFollowersOfUser(userId, after).awaitResponse() if(response.isSuccessful) { - response.body()?.let { emit(response.body()!!) } + response.body()?.let { emit(response.body()!!.map { ProfileListItem.fromDto(it) }) } } } - override fun getFollowingsOfUser(userId: Int): Flow> = flow { - val response = followersApi.getFollowingsOfUser(userId).awaitResponse() + override fun getFollowingsOfUser(userId: Int, after: Int): Flow> = flow { + val response = followersApi.getFollowingsOfUser(userId, after).awaitResponse() if(response.isSuccessful) { - + response.body()?.let { emit(response.body()!!.map { ProfileListItem.fromDto(it) }) } } } override fun followUser(userId: Int): Flow = flow { - + val response = followersApi.followUser(IntIdentificationWrapper(userId)).awaitResponse() + if(response.isSuccessful) { + emit(true) + } else { + emit(false) + } } override fun unfollowUser(userId: Int): Flow = flow { - + val response = followersApi.unfollowUser(IntIdentificationWrapper(userId)).awaitResponse() + if(response.isSuccessful) { + emit(true) + } else { + emit(false) + } } } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/followers/data/remote/FollowersApi.kt b/app/src/main/java/com/isolaatti/followers/data/remote/FollowersApi.kt index 91f81fe..420ad27 100644 --- a/app/src/main/java/com/isolaatti/followers/data/remote/FollowersApi.kt +++ b/app/src/main/java/com/isolaatti/followers/data/remote/FollowersApi.kt @@ -7,17 +7,18 @@ import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.POST import retrofit2.http.Path +import retrofit2.http.Query interface FollowersApi { @GET("Following/FollowersOf/{userId}") - fun getFollowersOfUser(@Path("userId") userId: Int): Call> + fun getFollowersOfUser(@Path("userId") userId: Int, @Query("lastId") lastId: Int): Call> @GET("Following/FollowingsOf/{userId}") - fun getFollowingsOfUser(@Path("userId") userId: Int): Call> + fun getFollowingsOfUser(@Path("userId") userId: Int, @Query("lastId") lastId: Int): Call> @POST("Following/Follow") - fun followUser(@Body id: IntIdentificationWrapper): Call + fun followUser(@Body id: IntIdentificationWrapper): Call @POST("Following/Unfollow") - fun unfollowUser(@Body id: IntIdentificationWrapper): Call + fun unfollowUser(@Body id: IntIdentificationWrapper): Call } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/followers/domain/FollowUserUseCase.kt b/app/src/main/java/com/isolaatti/followers/domain/FollowUserUseCase.kt new file mode 100644 index 0000000..1f36836 --- /dev/null +++ b/app/src/main/java/com/isolaatti/followers/domain/FollowUserUseCase.kt @@ -0,0 +1,14 @@ +package com.isolaatti.followers.domain + +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +class FollowUserUseCase @Inject constructor(private val followersRepository: FollowersRepository) { + fun follow(userId: Int): Flow { + return followersRepository.followUser(userId) + } + + fun unfollow(userId: Int): Flow { + return followersRepository.unfollowUser(userId) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/followers/domain/FollowersRepository.kt b/app/src/main/java/com/isolaatti/followers/domain/FollowersRepository.kt index 3bfe091..6147570 100644 --- a/app/src/main/java/com/isolaatti/followers/domain/FollowersRepository.kt +++ b/app/src/main/java/com/isolaatti/followers/domain/FollowersRepository.kt @@ -1,11 +1,11 @@ package com.isolaatti.followers.domain -import com.isolaatti.profile.data.remote.ProfileListItemDto +import com.isolaatti.profile.domain.entity.ProfileListItem import kotlinx.coroutines.flow.Flow interface FollowersRepository { - fun getFollowersOfUser(userId: Int): Flow> - fun getFollowingsOfUser(userId: Int): Flow> + fun getFollowersOfUser(userId: Int, after: Int): Flow> + fun getFollowingsOfUser(userId: Int, after: Int): Flow> fun followUser(userId: Int): Flow fun unfollowUser(userId: Int): Flow } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/followers/presentation/FollowersViewModel.kt b/app/src/main/java/com/isolaatti/followers/presentation/FollowersViewModel.kt index 0da6380..9fae433 100644 --- a/app/src/main/java/com/isolaatti/followers/presentation/FollowersViewModel.kt +++ b/app/src/main/java/com/isolaatti/followers/presentation/FollowersViewModel.kt @@ -1,16 +1,120 @@ package com.isolaatti.followers.presentation +import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import com.isolaatti.profile.data.remote.ProfileListItemDto +import androidx.lifecycle.viewModelScope +import com.isolaatti.followers.domain.FollowUserUseCase +import com.isolaatti.followers.domain.FollowersRepository +import com.isolaatti.profile.domain.entity.ProfileListItem 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 FollowersViewModel @Inject constructor() : ViewModel() { +class FollowersViewModel @Inject constructor(private val followersRepository: FollowersRepository, private val followUserUseCase: FollowUserUseCase) : ViewModel() { + var userId: Int = 0 - val followers: MutableLiveData> = MutableLiveData() - val followings: MutableLiveData> = MutableLiveData() + private val followersList: MutableList = mutableListOf() + private val followingsList: MutableList = mutableListOf() + private val _followers: MutableLiveData> = MutableLiveData() + private val _followings: MutableLiveData> = MutableLiveData() + + val followers: LiveData> get() = _followers + val followings: LiveData> get() = _followings + + + private fun getFollowersLastId(): Int { + if(followersList.isEmpty()) { + return 0 + } + + return followersList.last().id + } + + private fun getFollowingsLastId(): Int { + if(followingsList.isEmpty()) { + return 0 + } + + return followingsList.last().id + } + + fun fetchFollowers() { + if(userId <= 0) { + return + } + + viewModelScope.launch { + followersRepository.getFollowersOfUser(userId, getFollowersLastId()).onEach { + followersList.addAll(it) + _followers.postValue(followersList) + }.flowOn(Dispatchers.IO).launchIn(this) + } + + } + + fun fetchFollowings() { + if(userId <= 0) { + return + } + + viewModelScope.launch { + followersRepository.getFollowingsOfUser(userId, getFollowingsLastId()).onEach { + followingsList.addAll(it) + _followings.postValue(followingsList) + }.flowOn(Dispatchers.IO).launchIn(this) + } + } + + private fun replaceOnLists(user: ProfileListItem) { + val followersIndex = followersList.indexOf(user) + val followingsIndex = followingsList.indexOf(user) + + if(followersIndex >= 0) { + followersList[followersIndex] = user + + _followers.postValue(followersList) + } + + if(followingsIndex >= 0) { + followingsList[followingsIndex] = user + + _followings.postValue(followingsList) + } + } + + fun followUser(user: ProfileListItem) { + user.updatingFollowState = true + + replaceOnLists(user) + + viewModelScope.launch { + followUserUseCase.follow(user.id).onEach { + user.following = true + user.updatingFollowState = false + replaceOnLists(user) + }.flowOn(Dispatchers.IO).launchIn(this) + } + } + + fun unfollowUser(user: ProfileListItem) { + user.updatingFollowState = true + + replaceOnLists(user) + + viewModelScope.launch { + followUserUseCase.unfollow(user.id).onEach { + user.following = false + user.updatingFollowState = false + replaceOnLists(user) + }.flowOn(Dispatchers.IO).launchIn(this) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/followers/ui/FollowersFragment.kt b/app/src/main/java/com/isolaatti/followers/ui/FollowersFragment.kt index 27a13a4..3285b7f 100644 --- a/app/src/main/java/com/isolaatti/followers/ui/FollowersFragment.kt +++ b/app/src/main/java/com/isolaatti/followers/ui/FollowersFragment.kt @@ -6,12 +6,17 @@ 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.FragmentFollowersBinding import com.isolaatti.followers.presentation.FollowersViewModel +import com.isolaatti.profile.domain.entity.ProfileListItem -class FollowersFragment : Fragment() { +class FollowersFragment : Fragment(), UserItemCallback { private lateinit var binding: FragmentFollowersBinding private val viewModel: FollowersViewModel by viewModels({ requireParentFragment() }) + private lateinit var adapter: UserListRecyclerViewAdapter override fun onCreateView( inflater: LayoutInflater, @@ -21,4 +26,35 @@ class FollowersFragment : Fragment() { binding = FragmentFollowersBinding.inflate(inflater) return binding.root } + + private fun setObservers() { + viewModel.followers.observe(viewLifecycleOwner) { + adapter.submitList(it) + } + } + + private fun bind() { + adapter = UserListRecyclerViewAdapter(this) + binding.recyclerUsers.adapter = adapter + binding.recyclerUsers.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false) + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + bind() + setObservers() + + viewModel.fetchFollowers() + } + + override fun itemClick(userId: Int) { + + } + + override fun followButtonClick(user: ProfileListItem, action: UserItemCallback.FollowButtonAction) { + when(action) { + UserItemCallback.FollowButtonAction.Follow -> viewModel.followUser(user) + UserItemCallback.FollowButtonAction.Unfollow -> viewModel.unfollowUser(user) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/followers/ui/FollowingFragment.kt b/app/src/main/java/com/isolaatti/followers/ui/FollowingFragment.kt index 3f22d08..0a66920 100644 --- a/app/src/main/java/com/isolaatti/followers/ui/FollowingFragment.kt +++ b/app/src/main/java/com/isolaatti/followers/ui/FollowingFragment.kt @@ -6,12 +6,17 @@ 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.FragmentFollowersBinding import com.isolaatti.followers.presentation.FollowersViewModel +import com.isolaatti.profile.domain.entity.ProfileListItem -class FollowingFragment : Fragment() { +class FollowingFragment : Fragment(), UserItemCallback { private lateinit var binding: FragmentFollowersBinding private val viewModel: FollowersViewModel by viewModels({ requireParentFragment() }) + private lateinit var adapter: UserListRecyclerViewAdapter override fun onCreateView( inflater: LayoutInflater, @@ -21,4 +26,35 @@ class FollowingFragment : Fragment() { binding = FragmentFollowersBinding.inflate(inflater) return binding.root } + + private fun setObservers() { + viewModel.followings.observe(viewLifecycleOwner) { + adapter.submitList(it) + } + } + + private fun bind() { + adapter = UserListRecyclerViewAdapter(this) + binding.recyclerUsers.adapter = adapter + binding.recyclerUsers.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false) + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + bind() + setObservers() + + viewModel.fetchFollowings() + } + + override fun itemClick(userId: Int) { + + } + + override fun followButtonClick(user: ProfileListItem, action: UserItemCallback.FollowButtonAction) { + when(action) { + UserItemCallback.FollowButtonAction.Follow -> viewModel.followUser(user) + UserItemCallback.FollowButtonAction.Unfollow -> viewModel.unfollowUser(user) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/followers/ui/MainFollowersFragment.kt b/app/src/main/java/com/isolaatti/followers/ui/MainFollowersFragment.kt index 78f3146..95d9df7 100644 --- a/app/src/main/java/com/isolaatti/followers/ui/MainFollowersFragment.kt +++ b/app/src/main/java/com/isolaatti/followers/ui/MainFollowersFragment.kt @@ -32,6 +32,10 @@ class MainFollowersFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + arguments?.getInt(ARGUMENT_USER_ID)?.let { + viewModel.userId = it + } + binding.viewPagerFollowersMain.adapter = FollowersViewPagerAdapter(this) TabLayoutMediator(binding.tabLayoutFollowers, binding.viewPagerFollowersMain) { tab, position -> when(position) { @@ -40,4 +44,8 @@ class MainFollowersFragment : Fragment() { } }.attach() } + + companion object { + const val ARGUMENT_USER_ID = "userId" + } } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/home/FeedFragment.kt b/app/src/main/java/com/isolaatti/home/FeedFragment.kt index ceeb6a1..5837c62 100644 --- a/app/src/main/java/com/isolaatti/home/FeedFragment.kt +++ b/app/src/main/java/com/isolaatti/home/FeedFragment.kt @@ -15,6 +15,7 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.fragment.app.activityViewModels import androidx.lifecycle.Observer import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView import com.isolaatti.BuildConfig import com.isolaatti.R import com.isolaatti.common.ErrorMessageViewModel @@ -131,18 +132,12 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback { viewBinding.feedRecyclerView.adapter = adapter viewBinding.feedRecyclerView.layoutManager = LinearLayoutManager(requireContext()) -// viewBinding.refreshButton.setOnClickListener { -// viewModel.getFeed(refresh = true) -// } viewBinding.swipeToRefresh.setOnRefreshListener { viewModel.getFeed(refresh = true) viewBinding.swipeToRefresh.isRefreshing = false } -// viewBinding.loadMoreButton.setOnClickListener { -// viewModel.getFeed(refresh = false) -// } viewBinding.topAppBar.setOnMenuItemClickListener { when(it.itemId) { @@ -174,11 +169,12 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback { viewModel.posts.observe(viewLifecycleOwner){ if (it?.first != null) { adapter.updateList(it.first!!, it.second) + adapter.newContentRequestFinished() } } viewModel.loadingPosts.observe(viewLifecycleOwner) { - viewBinding.swipeToRefresh.isRefreshing = it + viewBinding.loadingIndicator.visibility = if(it) View.VISIBLE else View.GONE } @@ -215,4 +211,8 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback { override fun onProfileClick(userId: Int) { ProfileActivity.startActivity(requireContext(), userId) } + + override fun onLoadMore() { + viewModel.getFeed(false) + } } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/home/presentation/FeedViewModel.kt b/app/src/main/java/com/isolaatti/home/presentation/FeedViewModel.kt index 9245a2d..4000715 100644 --- a/app/src/main/java/com/isolaatti/home/presentation/FeedViewModel.kt +++ b/app/src/main/java/com/isolaatti/home/presentation/FeedViewModel.kt @@ -40,13 +40,15 @@ class FeedViewModel @Inject constructor( } is Resource.Loading -> { - loadingPosts.postValue(true) + if(!refresh) + loadingPosts.postValue(true) } is Resource.Error -> { errorLoading.postValue(feedDtoResource.errorType) } } + isLoadingFromScrolling = false }.flowOn(Dispatchers.IO).launchIn(this) } } diff --git a/app/src/main/java/com/isolaatti/posting/comments/presentation/BottomSheetPostComments.kt b/app/src/main/java/com/isolaatti/posting/comments/presentation/BottomSheetPostComments.kt index 56c9575..97a40f6 100644 --- a/app/src/main/java/com/isolaatti/posting/comments/presentation/BottomSheetPostComments.kt +++ b/app/src/main/java/com/isolaatti/posting/comments/presentation/BottomSheetPostComments.kt @@ -124,4 +124,8 @@ class BottomSheetPostComments() : BottomSheetDialogFragment(), OnUserInteractedC override fun onProfileClick(userId: Int) { ProfileActivity.startActivity(requireContext(), userId) } + + override fun onLoadMore() { + TODO("Not yet implemented") + } } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/posting/common/domain/OnUserInteractedCallback.kt b/app/src/main/java/com/isolaatti/posting/common/domain/OnUserInteractedCallback.kt index f71ab16..1f1c1c5 100644 --- a/app/src/main/java/com/isolaatti/posting/common/domain/OnUserInteractedCallback.kt +++ b/app/src/main/java/com/isolaatti/posting/common/domain/OnUserInteractedCallback.kt @@ -3,4 +3,5 @@ package com.isolaatti.posting.common.domain interface OnUserInteractedCallback { fun onOptions(postId: Long) fun onProfileClick(userId: Int) + fun onLoadMore() } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/posting/posts/presentation/PostListingViewModelBase.kt b/app/src/main/java/com/isolaatti/posting/posts/presentation/PostListingViewModelBase.kt index 7d9e716..08a63a4 100644 --- a/app/src/main/java/com/isolaatti/posting/posts/presentation/PostListingViewModelBase.kt +++ b/app/src/main/java/com/isolaatti/posting/posts/presentation/PostListingViewModelBase.kt @@ -29,6 +29,7 @@ abstract class PostListingViewModelBase : ViewModel() { val noMoreContent = MutableLiveData(false) val errorLoading: MutableLiveData = MutableLiveData() + var isLoadingFromScrolling = false fun getLastId(): Long = try { posts.value?.first?.data?.last()?.post?.id ?: 0 } catch (e: NoSuchElementException) { 0 } diff --git a/app/src/main/java/com/isolaatti/posting/posts/presentation/PostsRecyclerViewAdapter.kt b/app/src/main/java/com/isolaatti/posting/posts/presentation/PostsRecyclerViewAdapter.kt index ebfcd4a..5416c2b 100644 --- a/app/src/main/java/com/isolaatti/posting/posts/presentation/PostsRecyclerViewAdapter.kt +++ b/app/src/main/java/com/isolaatti/posting/posts/presentation/PostsRecyclerViewAdapter.kt @@ -167,7 +167,23 @@ class PostsRecyclerViewAdapter (private val markwon: Markwon, private val callba override fun onBindViewHolder(holder: FeedViewHolder, position: Int) {} + private var requestedNewContent = false + + /** + * Call this method when new content has been added on onLoadMore() callback + */ + fun newContentRequestFinished() { + requestedNewContent = false + } override fun onBindViewHolder(holder: FeedViewHolder, position: Int, payloads: List) { holder.bindView(feedDto?.data?.get(position) ?: return, payloads) + val totalItems = feedDto?.data?.size + if(totalItems != null && totalItems > 0 && !requestedNewContent) { + if(position == totalItems - 1) { + requestedNewContent = true + callback.onLoadMore() + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/profile/data/remote/ProfileListItemDto.kt b/app/src/main/java/com/isolaatti/profile/data/remote/ProfileListItemDto.kt index 05e3e53..88bfca8 100644 --- a/app/src/main/java/com/isolaatti/profile/data/remote/ProfileListItemDto.kt +++ b/app/src/main/java/com/isolaatti/profile/data/remote/ProfileListItemDto.kt @@ -3,5 +3,6 @@ package com.isolaatti.profile.data.remote data class ProfileListItemDto( val id: Int, val name: String, - val profileImageId: String? + val profileImageId: String?, + val following: Boolean ) \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/profile/domain/entity/ProfileListItem.kt b/app/src/main/java/com/isolaatti/profile/domain/entity/ProfileListItem.kt new file mode 100644 index 0000000..fdc6328 --- /dev/null +++ b/app/src/main/java/com/isolaatti/profile/domain/entity/ProfileListItem.kt @@ -0,0 +1,41 @@ +package com.isolaatti.profile.domain.entity + +import com.isolaatti.profile.data.remote.ProfileListItemDto + +class ProfileListItem( + val id: Int, + val name: String, + val profileImageId: String?, + var following: Boolean? +) { + + var updatingFollowState = false + + companion object { + fun fromDto(dto: ProfileListItemDto) = ProfileListItem( + dto.id,dto.name, dto.profileImageId, dto.following + ) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as 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 + } + + override fun hashCode(): Int { + var result = id + result = 31 * result + name.hashCode() + result = 31 * result + (profileImageId?.hashCode() ?: 0) + result = 31 * result + (following?.hashCode() ?: 0) + return result + } +} \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/profile/ui/ProfileMainFragment.kt b/app/src/main/java/com/isolaatti/profile/ui/ProfileMainFragment.kt index dba7cbb..7bdecf4 100644 --- a/app/src/main/java/com/isolaatti/profile/ui/ProfileMainFragment.kt +++ b/app/src/main/java/com/isolaatti/profile/ui/ProfileMainFragment.kt @@ -74,6 +74,7 @@ class ProfileMainFragment : Fragment() { private val postsObserver: Observer?> = Observer { if(it?.first != null) { postsAdapter.updateList(it.first!!, it.second) + postsAdapter.newContentRequestFinished() } } @@ -121,6 +122,10 @@ class ProfileMainFragment : Fragment() { private fun bind() { + if(userId == null) { + return + } + viewBinding.topAppBar.setNavigationOnClickListener { findNavController().popBackStack() @@ -142,7 +147,7 @@ class ProfileMainFragment : Fragment() { } viewBinding.goToFollowersBtn.setOnClickListener { - findNavController().navigate(ProfileMainFragmentDirections.actionDiscussionsFragmentToMainFollowersFragment()) + findNavController().navigate(ProfileMainFragmentDirections.actionDiscussionsFragmentToMainFollowersFragment(userId!!)) } viewBinding.feedRecyclerView.adapter = postsAdapter @@ -158,7 +163,7 @@ class ProfileMainFragment : Fragment() { viewModel.posts.observe(viewLifecycleOwner, postsObserver) viewModel.followingState.observe(viewLifecycleOwner, followingStateObserver) viewModel.loadingPosts.observe(viewLifecycleOwner) { - viewBinding.swipeToRefresh.isRefreshing = it + viewBinding.loadingIndicator.visibility = if(it) View.VISIBLE else View.GONE } } @@ -216,6 +221,10 @@ class ProfileMainFragment : Fragment() { override fun onProfileClick(userId: Int) { //ProfileActivity.startActivity(requireContext(), userId) } + + override fun onLoadMore() { + viewModel.getFeed(false) + } } } diff --git a/app/src/main/java/com/isolaatti/utils/UrlGen.kt b/app/src/main/java/com/isolaatti/utils/UrlGen.kt index aedc001..4167559 100644 --- a/app/src/main/java/com/isolaatti/utils/UrlGen.kt +++ b/app/src/main/java/com/isolaatti/utils/UrlGen.kt @@ -4,4 +4,5 @@ import com.isolaatti.connectivity.RetrofitClient.Companion.BASE_URL object UrlGen { fun userProfileImage(userId: Int) = "${BASE_URL}images/profile_image/of_user/$userId?mode=small" + fun imageUrl(imageId: String) = "${BASE_URL}images/image/${imageId}" } \ No newline at end of file diff --git a/app/src/main/res/layout-land/fragment_feed.xml b/app/src/main/res/layout-land/fragment_feed.xml index 148463f..ef03435 100644 --- a/app/src/main/res/layout-land/fragment_feed.xml +++ b/app/src/main/res/layout-land/fragment_feed.xml @@ -59,7 +59,12 @@ android:layout_height="match_parent" /> - + + - + diff --git a/app/src/main/res/layout/fragment_followers.xml b/app/src/main/res/layout/fragment_followers.xml index 77d9ef6..c74058a 100644 --- a/app/src/main/res/layout/fragment_followers.xml +++ b/app/src/main/res/layout/fragment_followers.xml @@ -3,4 +3,14 @@ android:layout_width="match_parent" android:layout_height="match_parent"> + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_followers_main.xml b/app/src/main/res/layout/fragment_followers_main.xml index be2d0b6..152f5cf 100644 --- a/app/src/main/res/layout/fragment_followers_main.xml +++ b/app/src/main/res/layout/fragment_followers_main.xml @@ -9,6 +9,7 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/profile_navigation.xml b/app/src/main/res/navigation/profile_navigation.xml index e91005b..e15bae0 100644 --- a/app/src/main/res/navigation/profile_navigation.xml +++ b/app/src/main/res/navigation/profile_navigation.xml @@ -25,7 +25,9 @@ app:destination="@id/userLinkFragment" /> + app:destination="@id/mainFollowersFragment"> + + + android:label="MainFollowersFragment"> + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 680ad26..29fcb7e 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -4,4 +4,5 @@ #7015ea @color/design_default_color_background #000000 + #BA0606 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c3ef012..b4a8f24 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -63,4 +63,5 @@ Followers: %s Following: %s K M + People \ No newline at end of file