diff --git a/app/src/main/java/com/isolaatti/home/FeedFragment.kt b/app/src/main/java/com/isolaatti/home/FeedFragment.kt index 3157dae..6959c75 100644 --- a/app/src/main/java/com/isolaatti/home/FeedFragment.kt +++ b/app/src/main/java/com/isolaatti/home/FeedFragment.kt @@ -24,7 +24,6 @@ import com.isolaatti.databinding.FragmentFeedBinding import com.isolaatti.drafts.ui.DraftsActivity import com.isolaatti.home.presentation.FeedViewModel import com.isolaatti.posting.PostViewerActivity -import com.isolaatti.posting.posts.presentation.PostsViewModel import com.isolaatti.posting.comments.presentation.BottomSheetPostComments import com.isolaatti.posting.common.domain.OnUserInteractedWithPostCallback import com.isolaatti.posting.common.options_bottom_sheet.domain.OptionClicked @@ -32,6 +31,7 @@ import com.isolaatti.posting.common.options_bottom_sheet.domain.Options import com.isolaatti.posting.common.options_bottom_sheet.presentation.BottomSheetPostOptionsViewModel import com.isolaatti.posting.common.options_bottom_sheet.ui.BottomSheetPostOptionsFragment import com.isolaatti.posting.posts.presentation.PostsRecyclerViewAdapter +import com.isolaatti.posting.posts.presentation.UpdateEvent import com.isolaatti.posting.posts.ui.CreatePostActivity import com.isolaatti.profile.ui.ProfileActivity import com.isolaatti.settings.ui.SettingsActivity @@ -53,9 +53,8 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback { const val CALLER_ID = 20 } - private val viewModel: PostsViewModel by activityViewModels() private val errorViewModel: ErrorMessageViewModel by activityViewModels() - private val screenViewModel: FeedViewModel by viewModels() + private val viewModel: FeedViewModel by activityViewModels() val optionsViewModel: BottomSheetPostOptionsViewModel by activityViewModels() private lateinit var viewBinding: FragmentFeedBinding @@ -158,7 +157,7 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback { } } - screenViewModel.userProfile.observe(viewLifecycleOwner) { + viewModel.userProfile.observe(viewLifecycleOwner) { val header = viewBinding.homeDrawer.getHeaderView(0) as? ConstraintLayout val image: ImageView? = header?.findViewById(R.id.profileImageView) @@ -175,34 +174,23 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback { viewModel.posts.observe(viewLifecycleOwner){ - if (it != null) { - adapter.updateList(it,null) + if (it.first != null) { + adapter.updateList(it.first!!, it.second) } } -// viewModel.loadingPosts.observe(viewLifecycleOwner) { -// viewBinding.progressBarLoading.visibility = if(it) View.VISIBLE else View.GONE -// viewBinding.loadMoreButton.visibility = if(it) View.GONE else View.VISIBLE -// } -// + viewModel.loadingPosts.observe(viewLifecycleOwner) { + viewBinding.swipeToRefresh.isRefreshing = it + } + // viewModel.noMoreContent.observe(viewLifecycleOwner) { -// val visibility = if(it) View.VISIBLE else View.GONE -// viewBinding.noMoreContentToShowTextView.visibility = visibility -// viewBinding.refreshButton.visibility = visibility -// viewBinding.loadMoreButton.visibility = if(it) View.GONE else View.VISIBLE +// // } viewModel.errorLoading.observe(viewLifecycleOwner) { errorViewModel.error.postValue(it) } - viewModel.postLiked.observe(viewLifecycleOwner) { - viewModel.posts.value?.let { feed -> - adapter.updateList( - feed, PostsRecyclerViewAdapter.UpdateEvent( - PostsRecyclerViewAdapter.UpdateEvent.UpdateType.POST_LIKED, it.postId)) - } - } - screenViewModel.getProfile() + viewModel.getProfile() optionsViewModel.optionClicked.observe(viewLifecycleOwner, optionsObserver) } @@ -210,8 +198,6 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback { fun onNewMenuItemClicked(v: View) { } - - override fun onLiked(postId: Long) = viewModel.likePost(postId) override fun onUnLiked(postId: Long) = viewModel.unLikePost(postId) @@ -234,5 +220,4 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback { override fun onProfileClick(userId: Int) { ProfileActivity.startActivity(requireContext(), userId) } - } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/home/HomeActivity.kt b/app/src/main/java/com/isolaatti/home/HomeActivity.kt index 5e9682d..486e75e 100644 --- a/app/src/main/java/com/isolaatti/home/HomeActivity.kt +++ b/app/src/main/java/com/isolaatti/home/HomeActivity.kt @@ -8,16 +8,13 @@ import androidx.lifecycle.Observer import androidx.navigation.fragment.NavHostFragment import androidx.navigation.ui.setupWithNavController import com.isolaatti.R -import com.isolaatti.common.ErrorMessageViewModel import com.isolaatti.common.IsolaattiBaseActivity import com.isolaatti.databinding.ActivityHomeBinding -import com.isolaatti.posting.posts.presentation.PostsViewModel -import com.isolaatti.utils.Resource -import dagger.hilt.android.AndroidEntryPoint +import com.isolaatti.home.presentation.FeedViewModel class HomeActivity : IsolaattiBaseActivity() { private lateinit var viewBinding: ActivityHomeBinding - private val postsViewModel: PostsViewModel by viewModels() + private val feedViewModel: FeedViewModel by viewModels() override fun onRetry() { } @@ -32,7 +29,7 @@ class HomeActivity : IsolaattiBaseActivity() { viewBinding.navigationRail?.setupWithNavController(navHostFragment.navController) if(savedInstanceState == null) { - postsViewModel.getFeed(false) + feedViewModel.getFeed(false) } } 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 aff554c..9245a2d 100644 --- a/app/src/main/java/com/isolaatti/home/presentation/FeedViewModel.kt +++ b/app/src/main/java/com/isolaatti/home/presentation/FeedViewModel.kt @@ -5,6 +5,9 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.isolaatti.auth.domain.AuthRepository +import com.isolaatti.posting.posts.domain.PostsRepository +import com.isolaatti.posting.posts.presentation.PostListingViewModelBase +import com.isolaatti.posting.posts.presentation.UpdateEvent import com.isolaatti.profile.data.remote.UserProfileDto import com.isolaatti.profile.domain.use_case.GetProfile import com.isolaatti.utils.Resource @@ -17,7 +20,36 @@ import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel -class FeedViewModel @Inject constructor(private val getProfileUseCase: GetProfile, private val authRepository: AuthRepository) : ViewModel() { +class FeedViewModel @Inject constructor( + private val getProfileUseCase: GetProfile, + private val authRepository: AuthRepository, + private val postsRepository: PostsRepository +) : PostListingViewModelBase() { + + override fun getFeed(refresh: Boolean) { + viewModelScope.launch { + if (refresh) { + posts.value = null + } + postsRepository.getFeed(getLastId()).onEach { feedDtoResource -> + when (feedDtoResource) { + is Resource.Success -> { + loadingPosts.postValue(false) + posts.postValue(Pair(posts.value?.first?.concatFeed(feedDtoResource.data) ?: feedDtoResource.data, UpdateEvent(UpdateEvent.UpdateType.PAGE_ADDED, null))) + noMoreContent.postValue(feedDtoResource.data?.moreContent == false) + } + + is Resource.Loading -> { + loadingPosts.postValue(true) + } + + is Resource.Error -> { + errorLoading.postValue(feedDtoResource.errorType) + } + } + }.flowOn(Dispatchers.IO).launchIn(this) + } + } // User profile private val _userProfile: MutableLiveData = MutableLiveData() @@ -25,10 +57,10 @@ class FeedViewModel @Inject constructor(private val getProfileUseCase: GetProfil fun getProfile() { viewModelScope.launch { - authRepository.getUserId().onEach {userId -> + authRepository.getUserId().onEach { userId -> userId?.let { - getProfileUseCase(userId).onEach {profile -> - if(profile is Resource.Success) { + getProfileUseCase(userId).onEach { profile -> + if (profile is Resource.Success) { profile.data?.let { _userProfile.postValue(it) } } }.flowOn(Dispatchers.IO).launchIn(this) diff --git a/app/src/main/java/com/isolaatti/posting/posts/data/repository/PostsRepositoryImpl.kt b/app/src/main/java/com/isolaatti/posting/posts/data/repository/PostsRepositoryImpl.kt index 9f728ac..deeddda 100644 --- a/app/src/main/java/com/isolaatti/posting/posts/data/repository/PostsRepositoryImpl.kt +++ b/app/src/main/java/com/isolaatti/posting/posts/data/repository/PostsRepositoryImpl.kt @@ -10,14 +10,8 @@ import com.isolaatti.posting.posts.data.remote.PostApi import com.isolaatti.posting.posts.domain.PostsRepository import com.isolaatti.utils.Resource import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.flow.flow -import retrofit2.await -import java.io.IOException -import java.lang.RuntimeException import javax.inject.Inject -import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine class PostsRepositoryImpl @Inject constructor(private val feedsApi: FeedsApi, private val postApi: PostApi) : PostsRepository { override fun getFeed(lastId: Long): Flow> = flow { @@ -39,7 +33,7 @@ class PostsRepositoryImpl @Inject constructor(private val feedsApi: FeedsApi, pr } } - override fun getProfilePosts(userId: Int, lastId: Long, olderFirst: Boolean, filter: FeedFilterDto): Flow> = flow { + override fun getProfilePosts(userId: Int, lastId: Long, olderFirst: Boolean, filter: FeedFilterDto?): Flow> = flow { emit(Resource.Loading()) try { val gson = Gson() diff --git a/app/src/main/java/com/isolaatti/posting/posts/domain/PostsRepository.kt b/app/src/main/java/com/isolaatti/posting/posts/domain/PostsRepository.kt index 69ca2b5..329a687 100644 --- a/app/src/main/java/com/isolaatti/posting/posts/domain/PostsRepository.kt +++ b/app/src/main/java/com/isolaatti/posting/posts/domain/PostsRepository.kt @@ -11,7 +11,7 @@ interface PostsRepository { fun getFeed(lastId: Long): Flow> - fun getProfilePosts(userId: Int, lastId: Long, olderFirst: Boolean, filter: FeedFilterDto): Flow> + fun getProfilePosts(userId: Int, lastId: Long, olderFirst: Boolean, filter: FeedFilterDto?): Flow> fun makePost(createPostDto: CreatePostDto): Flow> fun editPost(editPostDto: EditPostDto): Flow> diff --git a/app/src/main/java/com/isolaatti/posting/posts/presentation/PostListingRecyclerViewAdapterWiring.kt b/app/src/main/java/com/isolaatti/posting/posts/presentation/PostListingRecyclerViewAdapterWiring.kt new file mode 100644 index 0000000..006513a --- /dev/null +++ b/app/src/main/java/com/isolaatti/posting/posts/presentation/PostListingRecyclerViewAdapterWiring.kt @@ -0,0 +1,22 @@ +package com.isolaatti.posting.posts.presentation + +import com.isolaatti.posting.common.domain.OnUserInteractedWithPostCallback + +abstract class PostListingRecyclerViewAdapterWiring(private val postsViewModelBase: PostListingViewModelBase) : OnUserInteractedWithPostCallback { + + override fun onLiked(postId: Long) { + postsViewModelBase.likePost(postId) + } + + override fun onUnLiked(postId: Long) { + postsViewModelBase.unLikePost(postId) + } + + abstract override fun onComment(postId: Long) + + abstract override fun onOpenPost(postId: Long) + + abstract override fun onOptions(postId: Long) + + abstract override fun onProfileClick(userId: Int) +} \ 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 new file mode 100644 index 0000000..c7a616b --- /dev/null +++ b/app/src/main/java/com/isolaatti/posting/posts/presentation/PostListingViewModelBase.kt @@ -0,0 +1,72 @@ +package com.isolaatti.posting.posts.presentation + +import android.util.Log +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.isolaatti.posting.likes.data.remote.LikeDto +import com.isolaatti.posting.likes.domain.repository.LikesRepository +import com.isolaatti.posting.posts.data.remote.FeedDto +import com.isolaatti.profile.domain.use_case.GetProfilePosts +import com.isolaatti.utils.Resource +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 + +abstract class PostListingViewModelBase : ViewModel() { + @Inject + lateinit var likesRepository: LikesRepository + @Inject + lateinit var getProfilePosts: GetProfilePosts + + val posts: MutableLiveData> = MutableLiveData() + + val loadingPosts = MutableLiveData(false) + + val noMoreContent = MutableLiveData(false) + + val errorLoading: MutableLiveData = MutableLiveData() + + fun getLastId(): Long = try { posts.value?.first?.data?.last()?.post?.id ?: 0 } catch (e: NoSuchElementException) { 0 } + + + abstract fun getFeed(refresh: Boolean) + + fun likePost(postId: Long) { + viewModelScope.launch { + likesRepository.likePost(postId).onEach {likeDto -> + val likedPost = posts.value?.first?.data?.find { post -> post.post.id == likeDto.postId } + val index = posts.value?.first?.data?.indexOf(likedPost) + if(index != null){ + val temp = posts.value?.first + Log.d("***", temp.toString()) + temp?.data?.set(index, likedPost!!.apply { + liked = true + numberOfLikes = likeDto.likesCount + + }) + } + posts.postValue(posts.value?.copy(second = UpdateEvent(UpdateEvent.UpdateType.POST_LIKED, likedPost?.post?.id))) + }.flowOn(Dispatchers.IO).launchIn(this) + } + } + + fun unLikePost(postId: Long) { + viewModelScope.launch { + likesRepository.unLikePost(postId).onEach {likeDto -> + val likedPost = posts.value?.first?.data?.find { post -> post.post.id == likeDto.postId } + val index = posts.value?.first?.data?.indexOf(likedPost) + if(index != null){ + posts.value?.first?.data?.set(index, likedPost!!.apply { + liked = false + numberOfLikes = likeDto.likesCount + }) + } + posts.postValue(posts.value?.copy(second = UpdateEvent(UpdateEvent.UpdateType.POST_LIKED, likedPost?.post?.id))) + }.flowOn(Dispatchers.IO).launchIn(this) + } + } +} \ No newline at end of file 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 06b3551..ebfcd4a 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 @@ -114,15 +114,6 @@ class PostsRecyclerViewAdapter (private val markwon: Markwon, private val callba data class LikeCountUpdatePayload(val likeCount: Int) data class CommentsCountUpdatePayload(val commentsCount: Int) - data class UpdateEvent(val updateType: UpdateType, val affectedId: Long) { - enum class UpdateType { - POST_LIKED, - POST_COMMENTED, - POST_REMOVED, - POST_ADDED - } - } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FeedViewHolder { val view = LayoutInflater.from(parent.context).inflate(R.layout.post_layout, parent, false) @@ -143,24 +134,34 @@ class PostsRecyclerViewAdapter (private val markwon: Markwon, private val callba notifyDataSetChanged() return } - val postUpdated = feedDto?.data?.find { p -> p.post.id == updateEvent.affectedId } ?: return - val position = feedDto?.data?.indexOf(postUpdated) ?: return + val postUpdated = feedDto?.data?.find { p -> p.post.id == updateEvent.affectedId } + val position = feedDto?.data?.indexOf(postUpdated) feedDto = updatedFeed when(updateEvent.updateType) { UpdateEvent.UpdateType.POST_LIKED -> { - notifyItemChanged(position, LikeCountUpdatePayload(postUpdated.numberOfLikes)) + if(postUpdated != null && position != null) + notifyItemChanged(position, LikeCountUpdatePayload(postUpdated.numberOfLikes)) } UpdateEvent.UpdateType.POST_COMMENTED -> { - notifyItemChanged(position, CommentsCountUpdatePayload(postUpdated.numberOfComments)) + if(postUpdated != null && position != null) + notifyItemChanged(position, CommentsCountUpdatePayload(postUpdated.numberOfComments)) } UpdateEvent.UpdateType.POST_REMOVED -> { - notifyItemRemoved(position) + if(postUpdated != null && position != null) + notifyItemRemoved(position) } UpdateEvent.UpdateType.POST_ADDED -> { notifyItemInserted(0) } + + UpdateEvent.UpdateType.PAGE_ADDED -> { + notifyItemInserted(itemCount - 1) + } + UpdateEvent.UpdateType.REFRESH -> { + notifyDataSetChanged() + } } } diff --git a/app/src/main/java/com/isolaatti/posting/posts/presentation/PostsViewModel.kt b/app/src/main/java/com/isolaatti/posting/posts/presentation/PostsViewModel.kt deleted file mode 100644 index 3b953cc..0000000 --- a/app/src/main/java/com/isolaatti/posting/posts/presentation/PostsViewModel.kt +++ /dev/null @@ -1,105 +0,0 @@ -package com.isolaatti.posting.posts.presentation - -import android.util.Log -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.isolaatti.posting.comments.data.remote.FeedCommentsDto -import com.isolaatti.posting.posts.data.remote.FeedDto -import com.isolaatti.posting.likes.data.remote.LikeDto -import com.isolaatti.posting.likes.domain.repository.LikesRepository -import com.isolaatti.posting.posts.data.repository.PostsRepositoryImpl -import com.isolaatti.posting.posts.domain.PostsRepository -import com.isolaatti.utils.Resource -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.CoroutineScope -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 PostsViewModel @Inject constructor(private val postsRepository: PostsRepository, private val likesRepository: LikesRepository) : ViewModel() { - - private val _posts: MutableLiveData = MutableLiveData() - val posts: LiveData get() = _posts - - private val _loadingPosts = MutableLiveData(false) - val loadingPosts: LiveData get() = _loadingPosts - - private val _noMoreContent = MutableLiveData(false) - val noMoreContent: LiveData get() = _noMoreContent - - private val _errorLoading: MutableLiveData = MutableLiveData() - val errorLoading: LiveData get() = _errorLoading - - private val _comments: MutableLiveData = MutableLiveData() - val comments: LiveData get() = _comments - - private fun getLastId(): Long = try {_posts.value?.data?.last()?.post?.id ?: 0} catch (e: NoSuchElementException) { 0 } - - private val _postLiked: MutableLiveData = MutableLiveData() - val postLiked: LiveData get() = _postLiked - - fun getFeed(refresh: Boolean) { - viewModelScope.launch { - if(refresh) { - _posts.value = null - } - postsRepository.getFeed(getLastId()).onEach { - when(it) { - is Resource.Success -> { - _loadingPosts.postValue(false) - _posts.postValue(posts.value?.concatFeed(it.data) ?: it.data) - _noMoreContent.postValue(it.data?.moreContent == false) - } - is Resource.Loading -> { - _loadingPosts.postValue(true) - } - is Resource.Error -> { - _errorLoading.postValue(it.errorType) - } - } - }.flowOn(Dispatchers.IO).launchIn(this) - } - } - - fun likePost(postId: Long) { - viewModelScope.launch { - likesRepository.likePost(postId).onEach {likeDto -> - val likedPost = _posts.value?.data?.find { post -> post.post.id == likeDto.postId } - val index = _posts.value?.data?.indexOf(likedPost) - Log.d("***", index.toString()) - if(index != null){ - val temp = _posts.value - Log.d("***", temp.toString()) - temp?.data?.set(index, likedPost!!.apply { - liked = true - numberOfLikes = likeDto.likesCount - - }) - } - _postLiked.postValue(likeDto) - }.flowOn(Dispatchers.IO).launchIn(this) - } - } - - fun unLikePost(postId: Long) { - viewModelScope.launch { - likesRepository.unLikePost(postId).onEach {likeDto -> - val likedPost = _posts.value?.data?.find { post -> post.post.id == likeDto.postId } - val index = _posts.value?.data?.indexOf(likedPost) - if(index != null){ - _posts.value?.data?.set(index, likedPost!!.apply { - liked = false - numberOfLikes = likeDto.likesCount - }) - } - _postLiked.postValue(likeDto) - }.flowOn(Dispatchers.IO).launchIn(this) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/posting/posts/presentation/UpdateEvent.kt b/app/src/main/java/com/isolaatti/posting/posts/presentation/UpdateEvent.kt new file mode 100644 index 0000000..1e1c57d --- /dev/null +++ b/app/src/main/java/com/isolaatti/posting/posts/presentation/UpdateEvent.kt @@ -0,0 +1,12 @@ +package com.isolaatti.posting.posts.presentation + +data class UpdateEvent(val updateType: UpdateType, val affectedId: Long?) { + enum class UpdateType { + POST_LIKED, + POST_COMMENTED, + POST_REMOVED, + POST_ADDED, + PAGE_ADDED, + REFRESH + } +} \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/profile/domain/use_case/GetProfilePosts.kt b/app/src/main/java/com/isolaatti/profile/domain/use_case/GetProfilePosts.kt index 60e847d..2b30902 100644 --- a/app/src/main/java/com/isolaatti/profile/domain/use_case/GetProfilePosts.kt +++ b/app/src/main/java/com/isolaatti/profile/domain/use_case/GetProfilePosts.kt @@ -8,6 +8,6 @@ import kotlinx.coroutines.flow.Flow import javax.inject.Inject class GetProfilePosts @Inject constructor(private val postsRepository: PostsRepository) { - operator fun invoke(userId: Int, lastId: Long, olderFirst: Boolean, filter: FeedFilterDto): Flow> = + operator fun invoke(userId: Int, lastId: Long, olderFirst: Boolean, filter: FeedFilterDto?): Flow> = postsRepository.getProfilePosts(userId, lastId, olderFirst, filter) } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/profile/presentation/ProfileViewModel.kt b/app/src/main/java/com/isolaatti/profile/presentation/ProfileViewModel.kt index 607eeaf..91400bb 100644 --- a/app/src/main/java/com/isolaatti/profile/presentation/ProfileViewModel.kt +++ b/app/src/main/java/com/isolaatti/profile/presentation/ProfileViewModel.kt @@ -6,6 +6,8 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.isolaatti.posting.posts.data.remote.FeedDto import com.isolaatti.posting.posts.data.remote.FeedFilterDto +import com.isolaatti.posting.posts.presentation.PostListingViewModelBase +import com.isolaatti.posting.posts.presentation.UpdateEvent import com.isolaatti.profile.data.remote.UserProfileDto import com.isolaatti.profile.domain.ProfileRepository import com.isolaatti.profile.domain.use_case.GetProfile @@ -24,14 +26,13 @@ import java.time.Month import javax.inject.Inject @HiltViewModel -class ProfileViewModel @Inject constructor(private val getProfileUseCase: GetProfile, private val getProfilePosts: GetProfilePosts) : ViewModel() { +class ProfileViewModel @Inject constructor(private val getProfileUseCase: GetProfile, private val getProfilePostsUseCase: GetProfilePosts) : PostListingViewModelBase() { private val _profile = MutableLiveData() val profile: LiveData get() = _profile - private val _posts = MutableLiveData() - val posts: LiveData get() = _posts + var profileId: Int = 0 - fun getProfile(profileId: Int) { + fun getProfile() { viewModelScope.launch { getProfileUseCase(profileId).onEach { if(it is Resource.Success) { @@ -41,24 +42,23 @@ class ProfileViewModel @Inject constructor(private val getProfileUseCase: GetPro } } - fun getPosts(profileId: Int, refresh: Boolean) { + override fun getFeed(refresh: Boolean) { viewModelScope.launch { - getProfilePosts( - profileId, - -1, - false, - FeedFilterDto( - "both", - "both", - FeedFilterDto.DataRange( - false, - LocalDate.of(2020, 1, 1), - LocalDate.now() - ) - ) - ).onEach { - if(it is Resource.Success) { - _posts.postValue(it.data!!) + getProfilePostsUseCase(profileId, getLastId(), false, null).onEach { feedDtoResource -> + when (feedDtoResource) { + is Resource.Success -> { + loadingPosts.postValue(false) + posts.postValue(Pair(posts.value?.first?.concatFeed(feedDtoResource.data) ?: feedDtoResource.data, UpdateEvent(UpdateEvent.UpdateType.PAGE_ADDED, null))) + noMoreContent.postValue(feedDtoResource.data?.moreContent == false) + } + + is Resource.Loading -> { + loadingPosts.postValue(true) + } + + is Resource.Error -> { + errorLoading.postValue(feedDtoResource.errorType) + } } }.flowOn(Dispatchers.IO).launchIn(this) } diff --git a/app/src/main/java/com/isolaatti/profile/ui/DiscussionsFragment.kt b/app/src/main/java/com/isolaatti/profile/ui/DiscussionsFragment.kt index 5041a93..7483ccf 100644 --- a/app/src/main/java/com/isolaatti/profile/ui/DiscussionsFragment.kt +++ b/app/src/main/java/com/isolaatti/profile/ui/DiscussionsFragment.kt @@ -1,29 +1,36 @@ package com.isolaatti.profile.ui +import android.content.Context import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.activity.viewModels import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels import androidx.lifecycle.Observer import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.LinearLayoutManager -import com.google.android.material.appbar.AppBarLayout import com.isolaatti.BuildConfig import com.isolaatti.R import com.isolaatti.databinding.FragmentDiscussionsBinding +import com.isolaatti.home.FeedFragment +import com.isolaatti.posting.PostViewerActivity +import com.isolaatti.posting.comments.presentation.BottomSheetPostComments import com.isolaatti.posting.common.domain.OnUserInteractedWithPostCallback +import com.isolaatti.posting.common.options_bottom_sheet.domain.Options +import com.isolaatti.posting.common.options_bottom_sheet.presentation.BottomSheetPostOptionsViewModel +import com.isolaatti.posting.common.options_bottom_sheet.ui.BottomSheetPostOptionsFragment import com.isolaatti.posting.posts.data.remote.FeedDto +import com.isolaatti.posting.posts.presentation.PostListingRecyclerViewAdapterWiring import com.isolaatti.posting.posts.presentation.PostsRecyclerViewAdapter +import com.isolaatti.posting.posts.presentation.UpdateEvent import com.isolaatti.profile.data.remote.UserProfileDto import com.isolaatti.profile.presentation.ProfileViewModel import com.isolaatti.utils.PicassoImagesPluginDef import com.isolaatti.utils.UrlGen import com.squareup.picasso.Picasso import dagger.hilt.android.AndroidEntryPoint -import dagger.hilt.android.lifecycle.HiltViewModel import io.noties.markwon.AbstractMarkwonPlugin import io.noties.markwon.Markwon import io.noties.markwon.MarkwonConfiguration @@ -31,14 +38,19 @@ import io.noties.markwon.image.destination.ImageDestinationProcessorRelativeToAb import io.noties.markwon.linkify.LinkifyPlugin @AndroidEntryPoint -class DiscussionsFragment : Fragment(), OnUserInteractedWithPostCallback { +class DiscussionsFragment : Fragment() { lateinit var viewBinding: FragmentDiscussionsBinding - private val viewModel: ProfileViewModel by viewModels() + val optionsViewModel: BottomSheetPostOptionsViewModel by activityViewModels() private var userId: Int? = null + lateinit var postsAdapter: PostsRecyclerViewAdapter + + // collapsing bar private var title = "" - lateinit var feedAdapter: PostsRecyclerViewAdapter + private var scrollRange = -1 + private var isShow = false + private val profileObserver = Observer { profile -> Picasso.get() @@ -50,30 +62,18 @@ class DiscussionsFragment : Fragment(), OnUserInteractedWithPostCallback { viewBinding.textViewDescription.text = profile.descriptionText } - private val postsObserver: Observer = Observer { - feedAdapter.updateList(it, null) + private val postsObserver: Observer> = Observer { + if(it.first != null) { + postsAdapter.updateList(it.first!!, it.second) + } + } - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) + private lateinit var postListingRecyclerViewAdapterWiring: PostListingRecyclerViewAdapterWiring - userId = (requireActivity()).intent.extras?.getInt(ProfileActivity.EXTRA_USER_ID) - } - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - viewBinding = FragmentDiscussionsBinding.inflate(inflater) + private fun setupCollapsingBar() { - return viewBinding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - var scrollRange = -1 - var isShow = false viewBinding.topAppBarLayout.addOnOffsetChangedListener { appBarLayout, verticalOffset -> if (scrollRange == -1) scrollRange = appBarLayout.totalScrollRange if (scrollRange + verticalOffset == 0) { @@ -83,38 +83,16 @@ class DiscussionsFragment : Fragment(), OnUserInteractedWithPostCallback { viewBinding.collapsingToolbarLayout.title = " " } } + } + + private fun bind() { + viewBinding.topAppBar.setNavigationOnClickListener { findNavController().popBackStack() } - viewModel.profile.observe(viewLifecycleOwner, profileObserver) - viewModel.posts.observe(viewLifecycleOwner, postsObserver) - - userId?.let { - viewModel.getProfile(it) - viewModel.getPosts(it, true) - } - - val markwon = Markwon.builder(requireContext()) - .usePlugin(object: AbstractMarkwonPlugin() { - override fun configureConfiguration(builder: MarkwonConfiguration.Builder) { - builder - .imageDestinationProcessor( - ImageDestinationProcessorRelativeToAbsolute - .create(BuildConfig.backend)) - } - }) - .usePlugin(PicassoImagesPluginDef.picassoImagePlugin) - .usePlugin(LinkifyPlugin.create()) - .build() - - feedAdapter = PostsRecyclerViewAdapter(markwon, this, null) - - viewBinding.feedRecyclerView.adapter = feedAdapter - viewBinding.feedRecyclerView.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false) - viewBinding.bottomAppBar.setOnMenuItemClickListener { when(it.itemId) { R.id.audios_menu_item -> { @@ -129,29 +107,88 @@ class DiscussionsFragment : Fragment(), OnUserInteractedWithPostCallback { } } + viewBinding.feedRecyclerView.adapter = postsAdapter + viewBinding.feedRecyclerView.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false) } - override fun onLiked(postId: Long) { - TODO("Not yet implemented") + private fun setObservers() { + viewModel.profile.observe(viewLifecycleOwner, profileObserver) + viewModel.posts.observe(viewLifecycleOwner, postsObserver) } - override fun onUnLiked(postId: Long) { - TODO("Not yet implemented") + private fun getData() { + + userId?.let { profileId -> + viewModel.profileId = profileId + viewModel.getProfile() + viewModel.getFeed(true) + } } - override fun onComment(postId: Long) { - TODO("Not yet implemented") + private fun setupPostsAdapter() { + val markwon = Markwon.builder(requireContext()) + .usePlugin(object: AbstractMarkwonPlugin() { + override fun configureConfiguration(builder: MarkwonConfiguration.Builder) { + builder + .imageDestinationProcessor( + ImageDestinationProcessorRelativeToAbsolute + .create(BuildConfig.backend)) + } + }) + .usePlugin(PicassoImagesPluginDef.picassoImagePlugin) + .usePlugin(LinkifyPlugin.create()) + .build() + + postsAdapter = PostsRecyclerViewAdapter(markwon,postListingRecyclerViewAdapterWiring, null ) + } - override fun onOpenPost(postId: Long) { - TODO("Not yet implemented") + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + userId = (requireActivity()).intent.extras?.getInt(ProfileActivity.EXTRA_USER_ID) } - override fun onOptions(postId: Long) { - TODO("Not yet implemented") + override fun onAttach(context: Context) { + super.onAttach(context) + postListingRecyclerViewAdapterWiring = object: PostListingRecyclerViewAdapterWiring(viewModel) { + override fun onComment(postId: Long) { + val modalBottomSheet = BottomSheetPostComments.getInstance(postId) + modalBottomSheet.show(requireActivity().supportFragmentManager, BottomSheetPostComments.TAG) + } + + override fun onOpenPost(postId: Long) { + PostViewerActivity.startActivity(requireContext(), postId) + } + + override fun onOptions(postId: Long) { + optionsViewModel.setOptions(Options.postOptions, FeedFragment.CALLER_ID) + val modalBottomSheet = BottomSheetPostOptionsFragment() + modalBottomSheet.show(requireActivity().supportFragmentManager, BottomSheetPostOptionsFragment.TAG) + } + + override fun onProfileClick(userId: Int) { + //ProfileActivity.startActivity(requireContext(), userId) + } + } } - override fun onProfileClick(userId: Int) { - TODO("Not yet implemented") + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + viewBinding = FragmentDiscussionsBinding.inflate(inflater) + + return viewBinding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupCollapsingBar() + setupPostsAdapter() + bind() + setObservers() + getData() } } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/profile/ui/ProfileActivity.kt b/app/src/main/java/com/isolaatti/profile/ui/ProfileActivity.kt index b464514..7979fdd 100644 --- a/app/src/main/java/com/isolaatti/profile/ui/ProfileActivity.kt +++ b/app/src/main/java/com/isolaatti/profile/ui/ProfileActivity.kt @@ -10,6 +10,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContentProviderCompat.requireContext import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity +import androidx.fragment.app.activityViewModels import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider import androidx.navigation.findNavController @@ -45,7 +46,6 @@ class ProfileActivity : FragmentActivity() { lateinit var viewBinding: ActivityProfileBinding - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) viewBinding = ActivityProfileBinding.inflate(layoutInflater) diff --git a/app/src/main/java/com/isolaatti/profile/ui/UserLinkFragment.kt b/app/src/main/java/com/isolaatti/profile/ui/UserLinkFragment.kt index 3ec6e0c..fc8d323 100644 --- a/app/src/main/java/com/isolaatti/profile/ui/UserLinkFragment.kt +++ b/app/src/main/java/com/isolaatti/profile/ui/UserLinkFragment.kt @@ -1,8 +1,24 @@ 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 com.isolaatti.databinding.FragmentUserlinkBinding import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint class UserLinkFragment : Fragment() { + lateinit var binding: FragmentUserlinkBinding + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + binding = FragmentUserlinkBinding.inflate(inflater) + + return binding.root + } } \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_audios.xml b/app/src/main/res/layout/fragment_audios.xml index 5a8b470..a475183 100644 --- a/app/src/main/res/layout/fragment_audios.xml +++ b/app/src/main/res/layout/fragment_audios.xml @@ -1,17 +1,18 @@ - - - - \ No newline at end of file + android:layout_height="match_parent" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_feed.xml b/app/src/main/res/layout/fragment_feed.xml index e11af16..231c2bc 100644 --- a/app/src/main/res/layout/fragment_feed.xml +++ b/app/src/main/res/layout/fragment_feed.xml @@ -44,9 +44,10 @@ android:id="@+id/feed_recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" /> - + + - - - + - - - - \ No newline at end of file + android:layout_height="wrap_content"> + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_userlink.xml b/app/src/main/res/layout/fragment_userlink.xml new file mode 100644 index 0000000..77d9ef6 --- /dev/null +++ b/app/src/main/res/layout/fragment_userlink.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file