feat(creator-channel): 댓글 수정 입력 모드를 구현한다
This commit is contained in:
@@ -4,11 +4,9 @@ import android.app.Activity
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.InputType
|
|
||||||
import android.text.method.ScrollingMovementMethod
|
import android.text.method.ScrollingMovementMethod
|
||||||
import android.widget.EditText
|
import android.view.inputmethod.InputMethodManager
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.widget.doAfterTextChanged
|
import androidx.core.widget.doAfterTextChanged
|
||||||
@@ -22,6 +20,7 @@ import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.player.Cr
|
|||||||
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.player.CreatorCommunityMediaPlayerManager
|
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.player.CreatorCommunityMediaPlayerManager
|
||||||
import kr.co.vividnext.sodalive.extensions.loadUrl
|
import kr.co.vividnext.sodalive.extensions.loadUrl
|
||||||
import kr.co.vividnext.sodalive.extensions.moneyFormat
|
import kr.co.vividnext.sodalive.extensions.moneyFormat
|
||||||
|
import kr.co.vividnext.sodalive.v2.components.modal.V2ModalDialog
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.reply.CreatorChannelCommunityReplyActivity
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.reply.CreatorChannelCommunityReplyActivity
|
||||||
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.ui.CreatorChannelCommunityCommentAdapter
|
import kr.co.vividnext.sodalive.v2.creator.channel.community.detail.ui.CreatorChannelCommunityCommentAdapter
|
||||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
@@ -39,11 +38,12 @@ class CreatorChannelCommunityDetailActivity : BaseActivity<ActivityCreatorChanne
|
|||||||
}
|
}
|
||||||
private val commentAdapter = CreatorChannelCommunityCommentAdapter(
|
private val commentAdapter = CreatorChannelCommunityCommentAdapter(
|
||||||
onReplyClick = { comment -> replyLauncher.launch(CreatorChannelCommunityReplyActivity.newIntent(this, postId, comment)) },
|
onReplyClick = { comment -> replyLauncher.launch(CreatorChannelCommunityReplyActivity.newIntent(this, postId, comment)) },
|
||||||
onModifyClick = { comment -> showModifyCommentDialog(comment.commentId, comment.comment) },
|
onModifyClick = { comment -> viewModel.startCommentEdit(comment.commentId, comment.comment) },
|
||||||
onDeleteClick = { comment -> showDeleteCommentDialog(comment.commentId) }
|
onDeleteClick = { comment -> showDeleteCommentDialog(comment.commentId) }
|
||||||
)
|
)
|
||||||
private lateinit var mediaPlayerManager: CreatorCommunityMediaPlayerManager
|
private lateinit var mediaPlayerManager: CreatorCommunityMediaPlayerManager
|
||||||
private var postId: Long = 0L
|
private var postId: Long = 0L
|
||||||
|
private var renderedEditingCommentId: Long? = null
|
||||||
|
|
||||||
override fun setupView() {
|
override fun setupView() {
|
||||||
mediaPlayerManager = CreatorCommunityMediaPlayerManager(this) { }
|
mediaPlayerManager = CreatorCommunityMediaPlayerManager(this) { }
|
||||||
@@ -70,18 +70,24 @@ class CreatorChannelCommunityDetailActivity : BaseActivity<ActivityCreatorChanne
|
|||||||
viewModel.updateCommentInput(editable?.toString().orEmpty())
|
viewModel.updateCommentInput(editable?.toString().orEmpty())
|
||||||
}
|
}
|
||||||
binding.btnCreatorChannelCommunityDetailSend.setOnClickListener { viewModel.submitComment() }
|
binding.btnCreatorChannelCommunityDetailSend.setOnClickListener { viewModel.submitComment() }
|
||||||
|
binding.btnCreatorChannelCommunityDetailEditCancel.setOnClickListener { viewModel.cancelCommentEdit() }
|
||||||
binding.ivCreatorChannelCommunityDetailHeart.setOnClickListener { viewModel.toggleLike() }
|
binding.ivCreatorChannelCommunityDetailHeart.setOnClickListener { viewModel.toggleLike() }
|
||||||
|
|
||||||
viewModel.detailStateLiveData.observe(this) { state -> render(state) }
|
viewModel.detailStateLiveData.observe(this) { state -> render(state) }
|
||||||
viewModel.commentWrittenEventLiveData.observe(this) { written ->
|
viewModel.commentWrittenEventLiveData.observe(this) { written ->
|
||||||
if (written == true) {
|
if (written == true) {
|
||||||
setResult(Activity.RESULT_OK)
|
setResult(Activity.RESULT_OK)
|
||||||
|
hideCommentKeyboard()
|
||||||
viewModel.consumeCommentWrittenEvent()
|
viewModel.consumeCommentWrittenEvent()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
viewModel.postChangedEventLiveData.observe(this) { changed ->
|
viewModel.postChangedEventLiveData.observe(this) { changed ->
|
||||||
if (changed == true) {
|
if (changed == true) {
|
||||||
setResult(Activity.RESULT_OK)
|
setResult(Activity.RESULT_OK)
|
||||||
|
val current = viewModel.detailStateLiveData.value as? CreatorChannelCommunityDetailUiState.Content
|
||||||
|
if (current?.editingCommentId != null) {
|
||||||
|
hideCommentKeyboard()
|
||||||
|
}
|
||||||
viewModel.consumePostChangedEvent()
|
viewModel.consumePostChangedEvent()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -171,6 +177,11 @@ class CreatorChannelCommunityDetailActivity : BaseActivity<ActivityCreatorChanne
|
|||||||
etCreatorChannelCommunityDetailComment.setText(content.commentInput)
|
etCreatorChannelCommunityDetailComment.setText(content.commentInput)
|
||||||
etCreatorChannelCommunityDetailComment.setSelection(content.commentInput.length)
|
etCreatorChannelCommunityDetailComment.setSelection(content.commentInput.length)
|
||||||
}
|
}
|
||||||
|
btnCreatorChannelCommunityDetailEditCancel.isVisible = content.editingCommentId != null
|
||||||
|
if (content.editingCommentId != null && content.editingCommentId != renderedEditingCommentId) {
|
||||||
|
showCommentKeyboard()
|
||||||
|
}
|
||||||
|
renderedEditingCommentId = content.editingCommentId
|
||||||
updateSendButton(content.commentInput.trim().isNotEmpty() && !content.isSendingComment)
|
updateSendButton(content.commentInput.trim().isNotEmpty() && !content.isSendingComment)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,35 +202,32 @@ class CreatorChannelCommunityDetailActivity : BaseActivity<ActivityCreatorChanne
|
|||||||
mediaPlayerManager.toggleContent(CreatorCommunityContentItem(post.postId, audioUrl))
|
mediaPlayerManager.toggleContent(CreatorCommunityContentItem(post.postId, audioUrl))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showModifyCommentDialog(commentId: Long, comment: String) {
|
private fun showDeleteCommentDialog(commentId: Long) {
|
||||||
val editText = EditText(this).apply {
|
V2ModalDialog(
|
||||||
inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE
|
activity = this,
|
||||||
minLines = 3
|
layoutInflater = layoutInflater,
|
||||||
setText(comment)
|
title = getString(R.string.confirm_delete_title),
|
||||||
setSelection(comment.length)
|
desc = getString(R.string.confirm_delete_message),
|
||||||
}
|
confirmButtonTitle = getString(R.string.confirm),
|
||||||
AlertDialog.Builder(this)
|
confirmButtonClick = { viewModel.deleteComment(commentId) },
|
||||||
.setTitle(R.string.menu_modify_action)
|
cancelButtonTitle = getString(R.string.cancel)
|
||||||
.setView(editText)
|
).show(screenWidth)
|
||||||
.setNegativeButton(R.string.cancel, null)
|
|
||||||
.setPositiveButton(R.string.confirm) { _, _ ->
|
|
||||||
val modifiedComment = editText.text?.toString().orEmpty()
|
|
||||||
if (modifiedComment.trim() == comment.trim()) {
|
|
||||||
Toast.makeText(this, R.string.audio_content_comment_no_change, Toast.LENGTH_SHORT).show()
|
|
||||||
} else {
|
|
||||||
viewModel.modifyComment(commentId, modifiedComment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.show()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showDeleteCommentDialog(commentId: Long) {
|
private fun showCommentKeyboard() {
|
||||||
AlertDialog.Builder(this)
|
val editText = binding.etCreatorChannelCommunityDetailComment
|
||||||
.setTitle(R.string.confirm_delete_title)
|
editText.requestFocus()
|
||||||
.setMessage(R.string.confirm_delete_message)
|
editText.post {
|
||||||
.setNegativeButton(R.string.cancel, null)
|
val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
.setPositiveButton(R.string.confirm) { _, _ -> viewModel.deleteComment(commentId) }
|
inputMethodManager.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT)
|
||||||
.show()
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hideCommentKeyboard() {
|
||||||
|
val editText = binding.etCreatorChannelCommunityDetailComment
|
||||||
|
val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
inputMethodManager.hideSoftInputFromWindow(editText.windowToken, 0)
|
||||||
|
editText.clearFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -128,6 +128,11 @@ class CreatorChannelCommunityDetailViewModel(
|
|||||||
fun submitComment() {
|
fun submitComment() {
|
||||||
val content = _detailStateLiveData.value as? CreatorChannelCommunityDetailUiState.Content ?: return
|
val content = _detailStateLiveData.value as? CreatorChannelCommunityDetailUiState.Content ?: return
|
||||||
val comment = content.commentInput.trim()
|
val comment = content.commentInput.trim()
|
||||||
|
val editingCommentId = content.editingCommentId
|
||||||
|
if (editingCommentId != null) {
|
||||||
|
submitCommentEdit(content, editingCommentId, comment)
|
||||||
|
return
|
||||||
|
}
|
||||||
if (!content.isCommentAvailable || comment.isBlank() || isSendingComment) return
|
if (!content.isCommentAvailable || comment.isBlank() || isSendingComment) return
|
||||||
|
|
||||||
isSendingComment = true
|
isSendingComment = true
|
||||||
@@ -175,6 +180,26 @@ class CreatorChannelCommunityDetailViewModel(
|
|||||||
_postChangedEventLiveData.value = false
|
_postChangedEventLiveData.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun startCommentEdit(commentId: Long, comment: String) {
|
||||||
|
val content = _detailStateLiveData.value as? CreatorChannelCommunityDetailUiState.Content ?: return
|
||||||
|
if (commentId <= 0) return
|
||||||
|
|
||||||
|
_detailStateLiveData.value = content.copy(
|
||||||
|
commentInput = comment,
|
||||||
|
editingCommentId = commentId,
|
||||||
|
editingOriginalComment = comment
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancelCommentEdit() {
|
||||||
|
val content = _detailStateLiveData.value as? CreatorChannelCommunityDetailUiState.Content ?: return
|
||||||
|
_detailStateLiveData.value = content.copy(
|
||||||
|
commentInput = "",
|
||||||
|
editingCommentId = null,
|
||||||
|
editingOriginalComment = ""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun modifyComment(commentId: Long, comment: String) {
|
fun modifyComment(commentId: Long, comment: String) {
|
||||||
val trimmedComment = comment.trim()
|
val trimmedComment = comment.trim()
|
||||||
if (commentId <= 0 || isModifyingComment) return
|
if (commentId <= 0 || isModifyingComment) return
|
||||||
@@ -192,6 +217,57 @@ class CreatorChannelCommunityDetailViewModel(
|
|||||||
modifyComment(ModifyCommentRequest(commentId = commentId, isActive = false))
|
modifyComment(ModifyCommentRequest(commentId = commentId, isActive = false))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun submitCommentEdit(
|
||||||
|
content: CreatorChannelCommunityDetailUiState.Content,
|
||||||
|
commentId: Long,
|
||||||
|
comment: String
|
||||||
|
) {
|
||||||
|
if (commentId <= 0 || isModifyingComment) return
|
||||||
|
if (comment.isBlank()) {
|
||||||
|
showBlankCommentToast()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (comment == content.editingOriginalComment.trim()) {
|
||||||
|
showNoChangeToast()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isModifyingComment = true
|
||||||
|
_detailStateLiveData.value = content.copy(isSendingComment = true)
|
||||||
|
compositeDisposable.add(
|
||||||
|
legacyRepository.modifyComment(ModifyCommentRequest(commentId = commentId, comment = comment), authToken())
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(
|
||||||
|
{ response ->
|
||||||
|
isModifyingComment = false
|
||||||
|
val current = _detailStateLiveData.value as? CreatorChannelCommunityDetailUiState.Content
|
||||||
|
?: return@subscribe
|
||||||
|
if (response.success) {
|
||||||
|
_postChangedEventLiveData.value = true
|
||||||
|
_detailStateLiveData.value = current.copy(
|
||||||
|
commentInput = "",
|
||||||
|
isSendingComment = false,
|
||||||
|
editingCommentId = null,
|
||||||
|
editingOriginalComment = ""
|
||||||
|
)
|
||||||
|
loadDetail(postId)
|
||||||
|
} else {
|
||||||
|
showMutationFailureToast(response.message)
|
||||||
|
_detailStateLiveData.value = current.copy(isSendingComment = false)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
isModifyingComment = false
|
||||||
|
val current = _detailStateLiveData.value as? CreatorChannelCommunityDetailUiState.Content
|
||||||
|
?: return@subscribe
|
||||||
|
showMutationFailureToast()
|
||||||
|
_detailStateLiveData.value = current.copy(isSendingComment = false)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleDetailResponse(response: ApiResponse<CreatorChannelCommunityPostDetailResponse>) {
|
private fun handleDetailResponse(response: ApiResponse<CreatorChannelCommunityPostDetailResponse>) {
|
||||||
val data = response.data
|
val data = response.data
|
||||||
if (!response.success || data == null) {
|
if (!response.success || data == null) {
|
||||||
@@ -313,6 +389,12 @@ class CreatorChannelCommunityDetailViewModel(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showNoChangeToast() {
|
||||||
|
_toastLiveData.value = CreatorChannelEvent(
|
||||||
|
ToastMessage(resId = R.string.audio_content_comment_no_change)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun CreatorChannelCommunityPostDetailResponse.toUiModel() = CreatorChannelCommunityPostDetailUiModel(
|
private fun CreatorChannelCommunityPostDetailResponse.toUiModel() = CreatorChannelCommunityPostDetailUiModel(
|
||||||
postId = postId,
|
postId = postId,
|
||||||
creatorId = creatorId,
|
creatorId = creatorId,
|
||||||
@@ -392,7 +474,9 @@ sealed interface CreatorChannelCommunityDetailUiState {
|
|||||||
val hasNextComment: Boolean,
|
val hasNextComment: Boolean,
|
||||||
val isLoadingComments: Boolean = false,
|
val isLoadingComments: Boolean = false,
|
||||||
val commentInput: String = "",
|
val commentInput: String = "",
|
||||||
val isSendingComment: Boolean = false
|
val isSendingComment: Boolean = false,
|
||||||
|
val editingCommentId: Long? = null,
|
||||||
|
val editingOriginalComment: String = ""
|
||||||
) : CreatorChannelCommunityDetailUiState
|
) : CreatorChannelCommunityDetailUiState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,18 +25,6 @@
|
|||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tv_creator_channel_community_detail_title"
|
|
||||||
style="@style/Typography.Body1"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:includeFontPadding="false"
|
|
||||||
android:text="@string/creator_channel_community_detail_title"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
<androidx.core.widget.NestedScrollView
|
<androidx.core.widget.NestedScrollView
|
||||||
@@ -335,5 +323,16 @@
|
|||||||
android:enabled="false"
|
android:enabled="false"
|
||||||
android:src="@drawable/ic_new_arrow_up_gray"
|
android:src="@drawable/ic_new_arrow_up_gray"
|
||||||
tools:background="@drawable/bg_creator_channel_community_send_enabled" />
|
tools:background="@drawable/bg_creator_channel_community_send_enabled" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/btn_creator_channel_community_detail_edit_cancel"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_marginStart="@dimen/spacing_8"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:contentDescription="@string/cancel"
|
||||||
|
android:src="@drawable/ic_x_white"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|||||||
Reference in New Issue
Block a user