fix(comment): 답글 더보기 Bottom Sheet 적용 및 삭제/신고 API 연동

답글 리스트에서 PopupMenu를 Bottom Sheet로 통일하고, 내 답글은 삭제, 타인 답글은 신고 메뉴만 노출하도록 변경.
삭제는 원 댓글 삭제와 동일한 API(deleteComment)를 사용하며, 신고는 reportComment로 연동.
This commit is contained in:
2025-08-20 15:55:59 +09:00
parent d1c62fd2b6
commit fdc9ba80e0
2 changed files with 98 additions and 44 deletions

View File

@@ -1,10 +1,8 @@
package kr.co.vividnext.sodalive.chat.character.comment
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.PopupMenu
import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding
import coil.load
@@ -15,8 +13,7 @@ import kr.co.vividnext.sodalive.databinding.ItemCharacterCommentReplyBinding
class CharacterCommentReplyAdapter(
private val currentUserId: Long,
private val onModify: (id: Long, newText: String, isReply: Boolean) -> Unit,
private val onDelete: (id: Long, isReply: Boolean) -> Unit
private val onMore: (data: CharacterReplyResponse, isOwner: Boolean) -> Unit
) : RecyclerView.Adapter<CharacterReplyVH>() {
// 첫 번째 아이템은 항상 원본 댓글
@@ -27,15 +24,21 @@ class CharacterCommentReplyAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CharacterReplyVH {
return if (viewType == 0) {
CharacterReplyHeaderVH(
ItemCharacterCommentBinding.inflate(LayoutInflater.from(parent.context), parent, false)
ItemCharacterCommentBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
} else {
CharacterReplyItemVH(
context = parent.context,
binding = ItemCharacterCommentReplyBinding.inflate(LayoutInflater.from(parent.context), parent, false),
binding = ItemCharacterCommentReplyBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
),
currentUserId = currentUserId,
onModify = onModify,
onDelete = onDelete
onMoreCallback = onMore
)
}
}
@@ -76,11 +79,9 @@ class CharacterReplyHeaderVH(
}
class CharacterReplyItemVH(
private val context: Context,
private val binding: ItemCharacterCommentReplyBinding,
private val currentUserId: Long,
private val onModify: (id: Long, newText: String, isReply: Boolean) -> Unit,
private val onDelete: (id: Long, isReply: Boolean) -> Unit
private val onMoreCallback: (data: CharacterReplyResponse, isOwner: Boolean) -> Unit
) : CharacterReplyVH(binding) {
override fun bind(item: Any) {
@@ -103,25 +104,8 @@ class CharacterReplyItemVH(
val isOwner = data.memberId == currentUserId
binding.ivMenu.visibility = View.VISIBLE
binding.ivMenu.setOnClickListener {
val popup = PopupMenu(context, it)
if (isOwner) {
popup.menuInflater.inflate(R.menu.content_comment_option_menu, popup.menu)
} else {
popup.menuInflater.inflate(R.menu.content_comment_option_menu2, popup.menu)
}
popup.setOnMenuItemClickListener { mi ->
when (mi.itemId) {
R.id.menu_review_modify -> {
// 간단화: 수정은 현재 텍스트 그대로 콜백 (실서비스는 인라인 에디트 UI 구성)
onModify(data.replyId, data.comment, true)
}
R.id.menu_review_delete -> {
onDelete(data.replyId, true)
}
}
true
}
popup.show()
// 답글의 더보기는 PopupMenu 대신 BottomSheet를 사용하기 위해 Fragment 측 콜백으로 위임
onMoreCallback(data, isOwner)
}
}
}

View File

@@ -8,6 +8,7 @@ import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import coil.load
@@ -140,20 +141,89 @@ class CharacterCommentReplyFragment : BaseFragment<FragmentCharacterCommentReply
adapter = CharacterCommentReplyAdapter(
currentUserId = SharedPreferenceManager.userId,
onModify = { id, newText, isReply ->
Toast.makeText(requireContext(), "수정 스텁: $id", Toast.LENGTH_SHORT).show()
},
onDelete = { id, isReply ->
val index = adapter.items.indexOfFirst {
when (it) {
is CharacterReplyResponse -> it.replyId == id
else -> false
onMore = { reply, isOwner ->
CharacterCommentMoreBottomSheet.newInstance(isOwner).apply {
onReport = {
val reportSheet = CharacterCommentReportBottomSheet.newInstance()
reportSheet.onSubmit = { reason ->
val token = "Bearer ${SharedPreferenceManager.token}"
val d = repository.reportComment(
characterId = characterId,
commentId = reply.replyId,
reason = reason,
token = token
).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ resp ->
if (resp.success) {
Toast.makeText(
requireContext(),
"신고가 접수되었습니다.",
Toast.LENGTH_SHORT
).show()
} else {
Toast.makeText(
requireContext(),
resp.message ?: "요청 중 오류가 발생했습니다",
Toast.LENGTH_SHORT
).show()
}
}, { e ->
Toast.makeText(
requireContext(),
e.message ?: "요청 중 오류가 발생했습니다",
Toast.LENGTH_SHORT
).show()
})
compositeDisposable.add(d)
}
reportSheet.show(parentFragmentManager, "reply_report")
}
}
if (index > 0) { // 0은 원본 댓글이므로 제외
adapter.items.removeAt(index)
adapter.notifyItemRemoved(index)
}
onDelete = {
AlertDialog.Builder(requireContext())
.setTitle(getString(R.string.confirm_delete_title))
.setMessage(getString(R.string.confirm_delete_message))
.setPositiveButton(getString(R.string.confirm)) { _, _ ->
val token = "Bearer ${SharedPreferenceManager.token}"
loadingDialog.show(screenWidth)
val d = repository.deleteComment(
characterId = characterId,
commentId = reply.replyId,
token = token
).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doFinally { loadingDialog.dismiss() }
.subscribe({ resp ->
if (resp.success) {
val index = adapter.items
.indexOfFirst {
it is CharacterReplyResponse
&& it.replyId == reply.replyId
}
if (index > 0) {
adapter.items.removeAt(index)
adapter.notifyItemRemoved(index)
}
} else {
Toast.makeText(
requireActivity(),
resp.message ?: "요청 중 오류가 발생했습니다",
Toast.LENGTH_SHORT
).show()
}
}, { e ->
Toast.makeText(
requireActivity(),
e.message ?: "요청 중 오류가 발생했습니다",
Toast.LENGTH_SHORT
).show()
})
compositeDisposable.add(d)
}
.setNegativeButton(getString(R.string.cancel), null)
.show()
}
}.show(childFragmentManager, "reply_more")
}
).apply {
items.clear()