From a325a1bbe9d1115bf15861bc8f1c2611393d1cc7 Mon Sep 17 00:00:00 2001 From: klaus Date: Thu, 9 Jul 2026 01:28:29 +0900 Subject: [PATCH] =?UTF-8?q?feat(creator-channel):=20=EB=8C=93=EA=B8=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EC=9E=85=EB=A0=A5=20=EB=AA=A8=EB=93=9C?= =?UTF-8?q?=EB=A5=BC=20=EA=B5=AC=ED=98=84=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CreatorChannelCommunityDetailActivity.kt | 70 ++++++++------- .../CreatorChannelCommunityDetailViewModel.kt | 86 ++++++++++++++++++- ...ivity_creator_channel_community_detail.xml | 23 +++-- 3 files changed, 135 insertions(+), 44 deletions(-) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/detail/CreatorChannelCommunityDetailActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/detail/CreatorChannelCommunityDetailActivity.kt index a175c3f7..01a402af 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/detail/CreatorChannelCommunityDetailActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/detail/CreatorChannelCommunityDetailActivity.kt @@ -4,11 +4,9 @@ import android.app.Activity import android.content.Context import android.content.Intent import android.os.Bundle -import android.text.InputType import android.text.method.ScrollingMovementMethod -import android.widget.EditText +import android.view.inputmethod.InputMethodManager import android.widget.Toast -import androidx.appcompat.app.AlertDialog import androidx.activity.result.contract.ActivityResultContracts import androidx.core.view.isVisible 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.extensions.loadUrl 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.ui.CreatorChannelCommunityCommentAdapter import org.koin.androidx.viewmodel.ext.android.viewModel @@ -39,11 +38,12 @@ class CreatorChannelCommunityDetailActivity : BaseActivity 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) } ) private lateinit var mediaPlayerManager: CreatorCommunityMediaPlayerManager private var postId: Long = 0L + private var renderedEditingCommentId: Long? = null override fun setupView() { mediaPlayerManager = CreatorCommunityMediaPlayerManager(this) { } @@ -70,18 +70,24 @@ class CreatorChannelCommunityDetailActivity : BaseActivity render(state) } viewModel.commentWrittenEventLiveData.observe(this) { written -> if (written == true) { setResult(Activity.RESULT_OK) + hideCommentKeyboard() viewModel.consumeCommentWrittenEvent() } } viewModel.postChangedEventLiveData.observe(this) { changed -> if (changed == true) { setResult(Activity.RESULT_OK) + val current = viewModel.detailStateLiveData.value as? CreatorChannelCommunityDetailUiState.Content + if (current?.editingCommentId != null) { + hideCommentKeyboard() + } viewModel.consumePostChangedEvent() } } @@ -171,6 +177,11 @@ class CreatorChannelCommunityDetailActivity : BaseActivity - 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) { + V2ModalDialog( + activity = this, + layoutInflater = layoutInflater, + title = getString(R.string.confirm_delete_title), + desc = getString(R.string.confirm_delete_message), + confirmButtonTitle = getString(R.string.confirm), + confirmButtonClick = { viewModel.deleteComment(commentId) }, + cancelButtonTitle = getString(R.string.cancel) + ).show(screenWidth) } - private fun showDeleteCommentDialog(commentId: Long) { - AlertDialog.Builder(this) - .setTitle(R.string.confirm_delete_title) - .setMessage(R.string.confirm_delete_message) - .setNegativeButton(R.string.cancel, null) - .setPositiveButton(R.string.confirm) { _, _ -> viewModel.deleteComment(commentId) } - .show() + private fun showCommentKeyboard() { + val editText = binding.etCreatorChannelCommunityDetailComment + editText.requestFocus() + editText.post { + val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + inputMethodManager.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT) + } + } + + 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 { diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/detail/CreatorChannelCommunityDetailViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/detail/CreatorChannelCommunityDetailViewModel.kt index 23dc22eb..947093a6 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/detail/CreatorChannelCommunityDetailViewModel.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/detail/CreatorChannelCommunityDetailViewModel.kt @@ -128,6 +128,11 @@ class CreatorChannelCommunityDetailViewModel( fun submitComment() { val content = _detailStateLiveData.value as? CreatorChannelCommunityDetailUiState.Content ?: return val comment = content.commentInput.trim() + val editingCommentId = content.editingCommentId + if (editingCommentId != null) { + submitCommentEdit(content, editingCommentId, comment) + return + } if (!content.isCommentAvailable || comment.isBlank() || isSendingComment) return isSendingComment = true @@ -175,6 +180,26 @@ class CreatorChannelCommunityDetailViewModel( _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) { val trimmedComment = comment.trim() if (commentId <= 0 || isModifyingComment) return @@ -192,6 +217,57 @@ class CreatorChannelCommunityDetailViewModel( 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) { val data = response.data 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( postId = postId, creatorId = creatorId, @@ -392,7 +474,9 @@ sealed interface CreatorChannelCommunityDetailUiState { val hasNextComment: Boolean, val isLoadingComments: Boolean = false, val commentInput: String = "", - val isSendingComment: Boolean = false + val isSendingComment: Boolean = false, + val editingCommentId: Long? = null, + val editingOriginalComment: String = "" ) : CreatorChannelCommunityDetailUiState } diff --git a/app/src/main/res/layout/activity_creator_channel_community_detail.xml b/app/src/main/res/layout/activity_creator_channel_community_detail.xml index 2d73ee76..0abb40b3 100644 --- a/app/src/main/res/layout/activity_creator_channel_community_detail.xml +++ b/app/src/main/res/layout/activity_creator_channel_community_detail.xml @@ -25,18 +25,6 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> - + +