WIP comentarios
This commit is contained in:
parent
bde81ee4e7
commit
4fc72786af
@ -23,7 +23,7 @@ import com.isolaatti.drafts.ui.DraftsActivity
|
||||
import com.isolaatti.home.presentation.FeedViewModel
|
||||
import com.isolaatti.picture_viewer.ui.PictureViewerActivity
|
||||
import com.isolaatti.posting.PostViewerActivity
|
||||
import com.isolaatti.posting.comments.presentation.BottomSheetPostComments
|
||||
import com.isolaatti.posting.comments.ui.BottomSheetPostComments
|
||||
import com.isolaatti.posting.common.domain.OnUserInteractedWithPostCallback
|
||||
import com.isolaatti.posting.common.domain.Ownable
|
||||
import com.isolaatti.posting.common.options_bottom_sheet.domain.OptionClicked
|
||||
|
||||
@ -9,7 +9,7 @@ import retrofit2.http.Query
|
||||
|
||||
interface CommentsApi {
|
||||
@POST("Posting/Post/{postId}/Comment")
|
||||
fun postComment(@Body commentToPost: CommentToPostDto): Call<Nothing>
|
||||
fun postComment(@Path("postId") postId: Long, @Body commentToPost: CommentToPostDto): Call<CommentDto>
|
||||
|
||||
@GET("Fetch/Post/{postId}/Comments")
|
||||
fun getCommentsOfPosts(@Path("postId") postId: Long, @Query("lastId") lastId: Long, @Query("take") count: Int): Call<FeedCommentsDto>
|
||||
|
||||
@ -5,6 +5,7 @@ import com.isolaatti.posting.comments.data.remote.CommentToPostDto
|
||||
import com.isolaatti.posting.comments.data.remote.CommentsApi
|
||||
import com.isolaatti.posting.comments.domain.CommentsRepository
|
||||
import com.isolaatti.posting.comments.domain.model.Comment
|
||||
import com.isolaatti.utils.Resource
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import retrofit2.awaitResponse
|
||||
@ -26,8 +27,17 @@ class CommentsRepositoryImpl @Inject constructor(private val commentsApi: Commen
|
||||
}
|
||||
}
|
||||
|
||||
override fun postComment(commentToPostDto: CommentToPostDto, postId: Long): Flow<Boolean> = flow {
|
||||
val response = commentsApi.postComment(commentToPostDto).awaitResponse()
|
||||
emit(response.isSuccessful)
|
||||
override fun postComment(content: String, audioId: String?, postId: Long): Flow<Resource<Comment>> = flow {
|
||||
emit(Resource.Loading())
|
||||
val commentToPostDto = CommentToPostDto(content, audioId)
|
||||
val response = commentsApi.postComment(postId, commentToPostDto).awaitResponse()
|
||||
if(response.isSuccessful) {
|
||||
val responseBody = response.body()
|
||||
if(responseBody != null) {
|
||||
emit(Resource.Success(Comment.fromCommentDto(responseBody)))
|
||||
return@flow
|
||||
}
|
||||
}
|
||||
emit(Resource.Error())
|
||||
}
|
||||
}
|
||||
@ -1,12 +1,12 @@
|
||||
package com.isolaatti.posting.comments.domain
|
||||
|
||||
import com.isolaatti.posting.comments.data.remote.CommentDto
|
||||
import com.isolaatti.posting.comments.data.remote.CommentToPostDto
|
||||
import com.isolaatti.posting.comments.domain.model.Comment
|
||||
import com.isolaatti.utils.Resource
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface CommentsRepository {
|
||||
fun getComments(postId: Long, lastId: Long): Flow<MutableList<Comment>>
|
||||
fun getComment(commentId: Long): Flow<CommentDto>
|
||||
fun postComment(commentToPostDto: CommentToPostDto, postId: Long): Flow<Boolean>
|
||||
fun postComment(content: String, audioId: String?, postId: Long): Flow<Resource<Comment>>
|
||||
}
|
||||
@ -26,5 +26,16 @@ data class Comment(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun fromCommentDto(dto: CommentDto): Comment {
|
||||
return Comment(
|
||||
id = dto.comment.id,
|
||||
textContent = dto.comment.textContent,
|
||||
userId = dto.comment.userId,
|
||||
postId = dto.comment.postId,
|
||||
date = dto.comment.date,
|
||||
username = dto.username
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,13 @@
|
||||
package com.isolaatti.posting.comments.domain.use_case
|
||||
|
||||
import com.isolaatti.posting.comments.domain.CommentsRepository
|
||||
import com.isolaatti.posting.comments.domain.model.Comment
|
||||
import com.isolaatti.utils.Resource
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import javax.inject.Inject
|
||||
|
||||
class PostComment @Inject constructor(private val commentsRepository: CommentsRepository) {
|
||||
operator fun invoke(content: String, postId: Long): Flow<Resource<Comment>> {
|
||||
return commentsRepository.postComment(content, null, postId)
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
package com.isolaatti.posting.comments.presentation
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
@ -7,12 +8,16 @@ import com.isolaatti.databinding.CommentLayoutBinding
|
||||
import com.isolaatti.posting.comments.data.remote.CommentDto
|
||||
import com.isolaatti.posting.comments.domain.model.Comment
|
||||
import com.isolaatti.posting.common.domain.OnUserInteractedCallback
|
||||
import com.isolaatti.posting.posts.presentation.PostsRecyclerViewAdapter
|
||||
import com.isolaatti.utils.UrlGen
|
||||
import com.squareup.picasso.Picasso
|
||||
import io.noties.markwon.Markwon
|
||||
|
||||
class CommentsRecyclerViewAdapter(private var list: List<Comment>, private val markwon: Markwon, private val callback: OnUserInteractedCallback) : RecyclerView.Adapter<CommentsRecyclerViewAdapter.CommentViewHolder>() {
|
||||
|
||||
private var previousSize = 0
|
||||
var blockInfiniteScroll = false
|
||||
|
||||
inner class CommentViewHolder(val viewBinding: CommentLayoutBinding) : RecyclerView.ViewHolder(viewBinding.root)
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CommentViewHolder {
|
||||
@ -21,6 +26,15 @@ class CommentsRecyclerViewAdapter(private var list: List<Comment>, private val m
|
||||
|
||||
override fun getItemCount(): Int = list.count()
|
||||
|
||||
private var requestedNewContent = false
|
||||
|
||||
/**
|
||||
* Call this method when new content has been added on onLoadMore() callback
|
||||
*/
|
||||
fun newContentRequestFinished() {
|
||||
requestedNewContent = false
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: CommentViewHolder, position: Int) {
|
||||
val comment = list[position]
|
||||
|
||||
@ -36,11 +50,53 @@ class CommentsRecyclerViewAdapter(private var list: List<Comment>, private val m
|
||||
Picasso.get()
|
||||
.load(UrlGen.userProfileImage(comment.userId))
|
||||
.into(holder.viewBinding.avatarPicture)
|
||||
|
||||
val totalItems = list.size
|
||||
if(totalItems > 0 && !requestedNewContent) {
|
||||
if(position == totalItems - 1 && !blockInfiniteScroll) {
|
||||
requestedNewContent = true
|
||||
callback.onLoadMore()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fun submitList(commentDtoList: List<Comment>) {
|
||||
val lastIndex = if(list.count() - 1 < 1) 0 else list.count() - 1
|
||||
list = commentDtoList
|
||||
notifyItemRangeChanged(lastIndex, commentDtoList.count())
|
||||
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun updateList(updatedList: List<Comment>, updateEvent: UpdateEvent? = null) {
|
||||
|
||||
if(updateEvent == null) {
|
||||
list = updatedList
|
||||
|
||||
notifyDataSetChanged()
|
||||
return
|
||||
}
|
||||
|
||||
val commentUpdated = updateEvent.affectedPosition?.let { list?.get(it) }
|
||||
val position = updateEvent.affectedPosition
|
||||
|
||||
previousSize = itemCount
|
||||
list = updatedList
|
||||
|
||||
|
||||
|
||||
when(updateEvent.updateType) {
|
||||
|
||||
UpdateEvent.UpdateType.COMMENT_REMOVED -> {
|
||||
if(commentUpdated != null && position != null)
|
||||
notifyItemRemoved(position)
|
||||
}
|
||||
UpdateEvent.UpdateType.COMMENT_ADDED_TOP -> {
|
||||
notifyItemInserted(0)
|
||||
}
|
||||
|
||||
UpdateEvent.UpdateType.COMMENT_PAGE_ADDED_BOTTOM -> {
|
||||
notifyItemInserted(previousSize)
|
||||
}
|
||||
UpdateEvent.UpdateType.REFRESH -> {
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -7,6 +7,8 @@ import androidx.lifecycle.viewModelScope
|
||||
import com.isolaatti.posting.comments.data.remote.CommentDto
|
||||
import com.isolaatti.posting.comments.domain.model.Comment
|
||||
import com.isolaatti.posting.comments.domain.use_case.GetComments
|
||||
import com.isolaatti.posting.comments.domain.use_case.PostComment
|
||||
import com.isolaatti.utils.Resource
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
@ -16,10 +18,14 @@ import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class CommentsViewModel @Inject constructor(private val getComments: GetComments) : ViewModel() {
|
||||
private val _comments: MutableLiveData<List<Comment>> = MutableLiveData()
|
||||
class CommentsViewModel @Inject constructor(private val getComments: GetComments, private val postComment: PostComment) : ViewModel() {
|
||||
private val commentsList: MutableList<Comment> = mutableListOf()
|
||||
|
||||
val comments: LiveData<List<Comment>> get() = _comments
|
||||
private val _comments: MutableLiveData<Pair<List<Comment>, UpdateEvent>> = MutableLiveData()
|
||||
|
||||
val comments: LiveData<Pair<List<Comment>, UpdateEvent>> get() = _comments
|
||||
val commentPosted: MutableLiveData<Boolean?> = MutableLiveData()
|
||||
val noMoreContent: MutableLiveData<Boolean?> = MutableLiveData()
|
||||
|
||||
/**
|
||||
* postId to query comments for. First page will be fetched when set.
|
||||
@ -33,12 +39,18 @@ class CommentsViewModel @Inject constructor(private val getComments: GetComments
|
||||
|
||||
private var lastId: Long = 0L
|
||||
|
||||
fun getContent() {
|
||||
fun getContent(refresh: Boolean = false) {
|
||||
viewModelScope.launch {
|
||||
if(refresh) {
|
||||
commentsList.clear()
|
||||
}
|
||||
getComments(postId, lastId).onEach {
|
||||
val newList = _comments.value?.toMutableList() ?: mutableListOf()
|
||||
newList.addAll(it)
|
||||
_comments.postValue(newList)
|
||||
val eventType = if((commentsList.isNotEmpty())) UpdateEvent.UpdateType.COMMENT_PAGE_ADDED_BOTTOM else UpdateEvent.UpdateType.REFRESH
|
||||
commentsList.addAll(it)
|
||||
_comments.postValue(Pair(commentsList, UpdateEvent(eventType, null)))
|
||||
if(it.isEmpty()) {
|
||||
noMoreContent.postValue(true)
|
||||
}
|
||||
if(it.isNotEmpty()){
|
||||
lastId = it.last().id
|
||||
}
|
||||
@ -47,13 +59,37 @@ class CommentsViewModel @Inject constructor(private val getComments: GetComments
|
||||
}
|
||||
}
|
||||
|
||||
fun postComment(content: String) {
|
||||
viewModelScope.launch {
|
||||
postComment(content, postId).onEach {
|
||||
when(it) {
|
||||
is Resource.Success -> {
|
||||
commentPosted.postValue(true)
|
||||
putCommentAtTheBeginning(it.data!!)
|
||||
}
|
||||
is Resource.Loading -> {
|
||||
|
||||
}
|
||||
is Resource.Error -> {
|
||||
commentPosted.postValue(false)
|
||||
}
|
||||
}
|
||||
}.flowOn(Dispatchers.IO).launchIn(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun handledCommentPosted() {
|
||||
commentPosted.postValue(null)
|
||||
}
|
||||
|
||||
/**
|
||||
* Use when new comment has been posted
|
||||
*/
|
||||
fun putCommentAtTheBeginning(commentDto: Comment) {
|
||||
val newList: MutableList<Comment> = mutableListOf(commentDto)
|
||||
newList.addAll(_comments.value ?: mutableListOf())
|
||||
_comments.postValue(newList)
|
||||
private fun putCommentAtTheBeginning(comment: Comment) {
|
||||
val newList: MutableList<Comment> = mutableListOf(comment)
|
||||
newList.addAll(commentsList)
|
||||
commentsList.clear()
|
||||
commentsList.addAll(newList)
|
||||
_comments.postValue(Pair(commentsList, UpdateEvent(UpdateEvent.UpdateType.COMMENT_ADDED_TOP, null)))
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
package com.isolaatti.posting.comments.presentation
|
||||
|
||||
|
||||
data class UpdateEvent(val updateType: UpdateType, val affectedPosition: Int?) {
|
||||
enum class UpdateType {
|
||||
COMMENT_REMOVED,
|
||||
COMMENT_ADDED_TOP,
|
||||
COMMENT_PAGE_ADDED_BOTTOM,
|
||||
REFRESH
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,11 @@
|
||||
package com.isolaatti.posting.comments.presentation
|
||||
package com.isolaatti.posting.comments.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Observer
|
||||
@ -13,15 +15,16 @@ import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import com.isolaatti.common.Dialogs
|
||||
import com.isolaatti.databinding.BottomSheetPostCommentsBinding
|
||||
import com.isolaatti.home.FeedFragment
|
||||
import com.isolaatti.posting.comments.domain.model.Comment
|
||||
import com.isolaatti.posting.comments.presentation.CommentsRecyclerViewAdapter
|
||||
import com.isolaatti.posting.comments.presentation.CommentsViewModel
|
||||
import com.isolaatti.posting.comments.presentation.UpdateEvent
|
||||
import com.isolaatti.posting.common.domain.OnUserInteractedCallback
|
||||
import com.isolaatti.posting.common.domain.Ownable
|
||||
import com.isolaatti.posting.common.options_bottom_sheet.domain.OptionClicked
|
||||
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.domain.entity.Post
|
||||
import com.isolaatti.profile.ui.ProfileActivity
|
||||
import com.isolaatti.utils.PicassoImagesPluginDef
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
@ -36,12 +39,12 @@ class BottomSheetPostComments() : BottomSheetDialogFragment(), OnUserInteractedC
|
||||
|
||||
private lateinit var viewBinding: BottomSheetPostCommentsBinding
|
||||
val viewModel: CommentsViewModel by viewModels()
|
||||
|
||||
private lateinit var adapter: CommentsRecyclerViewAdapter
|
||||
|
||||
val optionsViewModel: BottomSheetPostOptionsViewModel by activityViewModels()
|
||||
|
||||
val optionsObserver: Observer<OptionClicked?> = Observer { optionClicked ->
|
||||
if(optionClicked?.callerId == BottomSheetPostComments.CALLER_ID) {
|
||||
if(optionClicked?.callerId == CALLER_ID) {
|
||||
val comment = optionClicked.payload as? Comment ?: return@Observer
|
||||
when(optionClicked.optionId) {
|
||||
Options.Option.OPTION_DELETE -> {
|
||||
@ -65,6 +68,58 @@ class BottomSheetPostComments() : BottomSheetDialogFragment(), OnUserInteractedC
|
||||
|
||||
}
|
||||
|
||||
val commentPostedObserver: Observer<Boolean?> = Observer {
|
||||
when(it) {
|
||||
true -> {
|
||||
clearNewCommentUi()
|
||||
Toast.makeText(requireContext(), "comment posted", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
false -> {
|
||||
Toast.makeText(requireContext(), "comment failed to post", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
null -> return@Observer
|
||||
}
|
||||
|
||||
viewModel.handledCommentPosted()
|
||||
|
||||
}
|
||||
|
||||
private fun setObservers() {
|
||||
viewModel.comments.observe(viewLifecycleOwner) {
|
||||
val (list, updateEvent) = it
|
||||
adapter.updateList(list, updateEvent)
|
||||
if(updateEvent.updateType == UpdateEvent.UpdateType.COMMENT_ADDED_TOP) {
|
||||
(viewBinding.recyclerComments.layoutManager as LinearLayoutManager).scrollToPosition(0)
|
||||
} else {
|
||||
adapter.newContentRequestFinished()
|
||||
}
|
||||
}
|
||||
viewModel.noMoreContent.observe(viewLifecycleOwner) {
|
||||
if(it == true) {
|
||||
adapter.blockInfiniteScroll = true
|
||||
viewModel.noMoreContent.postValue(null)
|
||||
}
|
||||
|
||||
}
|
||||
optionsViewModel.optionClicked.observe(viewLifecycleOwner, optionsObserver)
|
||||
viewModel.commentPosted.observe(viewLifecycleOwner, commentPostedObserver)
|
||||
}
|
||||
|
||||
private fun setListeners() {
|
||||
viewBinding.newCommentTextField.editText?.doOnTextChanged { text, start, before, count ->
|
||||
viewBinding.submitCommentButton.isEnabled = !text.isNullOrBlank()
|
||||
}
|
||||
|
||||
viewBinding.submitCommentButton.setOnClickListener {
|
||||
val content = viewBinding.newCommentTextField.editText?.text ?: return@setOnClickListener
|
||||
viewModel.postComment(content.toString())
|
||||
}
|
||||
}
|
||||
|
||||
private fun clearNewCommentUi() {
|
||||
viewBinding.newCommentTextField.editText?.text?.clear()
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val postId = arguments?.getLong(ARG_POST_ID)
|
||||
@ -102,23 +157,16 @@ class BottomSheetPostComments() : BottomSheetDialogFragment(), OnUserInteractedC
|
||||
.usePlugin(LinkifyPlugin.create())
|
||||
.build()
|
||||
|
||||
val adapter = CommentsRecyclerViewAdapter(listOf(), markwon, this)
|
||||
adapter = CommentsRecyclerViewAdapter(listOf(), markwon, this)
|
||||
viewBinding.recyclerComments.adapter = adapter
|
||||
viewBinding.recyclerComments.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
|
||||
|
||||
viewModel.comments.observe(viewLifecycleOwner) {
|
||||
adapter.submitList(it)
|
||||
}
|
||||
|
||||
// New comment area
|
||||
val textField = viewBinding.newCommentTextField
|
||||
|
||||
textField.setStartIconOnClickListener {
|
||||
|
||||
}
|
||||
|
||||
optionsViewModel.optionClicked.observe(viewLifecycleOwner, optionsObserver)
|
||||
// ensures send button is enabled when there is content on text field,
|
||||
// even if no change event is triggered
|
||||
viewBinding.submitCommentButton.isEnabled = !viewBinding.newCommentTextField.editText?.text.isNullOrBlank()
|
||||
|
||||
setObservers()
|
||||
setListeners()
|
||||
}
|
||||
|
||||
companion object {
|
||||
@ -148,6 +196,6 @@ class BottomSheetPostComments() : BottomSheetDialogFragment(), OnUserInteractedC
|
||||
}
|
||||
|
||||
override fun onLoadMore() {
|
||||
TODO("Not yet implemented")
|
||||
viewModel.getContent()
|
||||
}
|
||||
}
|
||||
@ -19,13 +19,12 @@ import com.isolaatti.databinding.FragmentDiscussionsBinding
|
||||
import com.isolaatti.followers.domain.FollowingState
|
||||
import com.isolaatti.home.FeedFragment
|
||||
import com.isolaatti.posting.PostViewerActivity
|
||||
import com.isolaatti.posting.comments.presentation.BottomSheetPostComments
|
||||
import com.isolaatti.posting.comments.ui.BottomSheetPostComments
|
||||
import com.isolaatti.posting.common.domain.Ownable
|
||||
import com.isolaatti.posting.common.options_bottom_sheet.domain.OptionClicked
|
||||
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.domain.entity.Post
|
||||
import com.isolaatti.posting.posts.presentation.CreatePostContract
|
||||
import com.isolaatti.posting.posts.presentation.EditPostContract
|
||||
|
||||
@ -0,0 +1,4 @@
|
||||
package com.isolaatti.squads.ui
|
||||
|
||||
class SquadsActivity {
|
||||
}
|
||||
@ -80,6 +80,7 @@
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:enabled="false"
|
||||
app:icon="@drawable/baseline_send_24"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user