fix(comment): 답글 더보기 Bottom Sheet 적용 및 삭제/신고 API 연동
답글 리스트에서 PopupMenu를 Bottom Sheet로 통일하고, 내 답글은 삭제, 타인 답글은 신고 메뉴만 노출하도록 변경. 삭제는 원 댓글 삭제와 동일한 API(deleteComment)를 사용하며, 신고는 reportComment로 연동.
This commit is contained in:
		@@ -1,10 +1,8 @@
 | 
				
			|||||||
package kr.co.vividnext.sodalive.chat.character.comment
 | 
					package kr.co.vividnext.sodalive.chat.character.comment
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import android.content.Context
 | 
					 | 
				
			||||||
import android.view.LayoutInflater
 | 
					import android.view.LayoutInflater
 | 
				
			||||||
import android.view.View
 | 
					import android.view.View
 | 
				
			||||||
import android.view.ViewGroup
 | 
					import android.view.ViewGroup
 | 
				
			||||||
import android.widget.PopupMenu
 | 
					 | 
				
			||||||
import androidx.recyclerview.widget.RecyclerView
 | 
					import androidx.recyclerview.widget.RecyclerView
 | 
				
			||||||
import androidx.viewbinding.ViewBinding
 | 
					import androidx.viewbinding.ViewBinding
 | 
				
			||||||
import coil.load
 | 
					import coil.load
 | 
				
			||||||
@@ -15,8 +13,7 @@ import kr.co.vividnext.sodalive.databinding.ItemCharacterCommentReplyBinding
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class CharacterCommentReplyAdapter(
 | 
					class CharacterCommentReplyAdapter(
 | 
				
			||||||
    private val currentUserId: Long,
 | 
					    private val currentUserId: Long,
 | 
				
			||||||
    private val onModify: (id: Long, newText: String, isReply: Boolean) -> Unit,
 | 
					    private val onMore: (data: CharacterReplyResponse, isOwner: Boolean) -> Unit
 | 
				
			||||||
    private val onDelete: (id: Long, isReply: Boolean) -> Unit
 | 
					 | 
				
			||||||
) : RecyclerView.Adapter<CharacterReplyVH>() {
 | 
					) : RecyclerView.Adapter<CharacterReplyVH>() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 첫 번째 아이템은 항상 원본 댓글
 | 
					    // 첫 번째 아이템은 항상 원본 댓글
 | 
				
			||||||
@@ -27,15 +24,21 @@ class CharacterCommentReplyAdapter(
 | 
				
			|||||||
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CharacterReplyVH {
 | 
					    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CharacterReplyVH {
 | 
				
			||||||
        return if (viewType == 0) {
 | 
					        return if (viewType == 0) {
 | 
				
			||||||
            CharacterReplyHeaderVH(
 | 
					            CharacterReplyHeaderVH(
 | 
				
			||||||
                ItemCharacterCommentBinding.inflate(LayoutInflater.from(parent.context), parent, false)
 | 
					                ItemCharacterCommentBinding.inflate(
 | 
				
			||||||
 | 
					                    LayoutInflater.from(parent.context),
 | 
				
			||||||
 | 
					                    parent,
 | 
				
			||||||
 | 
					                    false
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            CharacterReplyItemVH(
 | 
					            CharacterReplyItemVH(
 | 
				
			||||||
                context = parent.context,
 | 
					                binding = ItemCharacterCommentReplyBinding.inflate(
 | 
				
			||||||
                binding = ItemCharacterCommentReplyBinding.inflate(LayoutInflater.from(parent.context), parent, false),
 | 
					                    LayoutInflater.from(parent.context),
 | 
				
			||||||
 | 
					                    parent,
 | 
				
			||||||
 | 
					                    false
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
                currentUserId = currentUserId,
 | 
					                currentUserId = currentUserId,
 | 
				
			||||||
                onModify = onModify,
 | 
					                onMoreCallback = onMore
 | 
				
			||||||
                onDelete = onDelete
 | 
					 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -76,11 +79,9 @@ class CharacterReplyHeaderVH(
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class CharacterReplyItemVH(
 | 
					class CharacterReplyItemVH(
 | 
				
			||||||
    private val context: Context,
 | 
					 | 
				
			||||||
    private val binding: ItemCharacterCommentReplyBinding,
 | 
					    private val binding: ItemCharacterCommentReplyBinding,
 | 
				
			||||||
    private val currentUserId: Long,
 | 
					    private val currentUserId: Long,
 | 
				
			||||||
    private val onModify: (id: Long, newText: String, isReply: Boolean) -> Unit,
 | 
					    private val onMoreCallback: (data: CharacterReplyResponse, isOwner: Boolean) -> Unit
 | 
				
			||||||
    private val onDelete: (id: Long, isReply: Boolean) -> Unit
 | 
					 | 
				
			||||||
) : CharacterReplyVH(binding) {
 | 
					) : CharacterReplyVH(binding) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun bind(item: Any) {
 | 
					    override fun bind(item: Any) {
 | 
				
			||||||
@@ -103,25 +104,8 @@ class CharacterReplyItemVH(
 | 
				
			|||||||
        val isOwner = data.memberId == currentUserId
 | 
					        val isOwner = data.memberId == currentUserId
 | 
				
			||||||
        binding.ivMenu.visibility = View.VISIBLE
 | 
					        binding.ivMenu.visibility = View.VISIBLE
 | 
				
			||||||
        binding.ivMenu.setOnClickListener {
 | 
					        binding.ivMenu.setOnClickListener {
 | 
				
			||||||
            val popup = PopupMenu(context, it)
 | 
					            // 답글의 더보기는 PopupMenu 대신 BottomSheet를 사용하기 위해 Fragment 측 콜백으로 위임
 | 
				
			||||||
            if (isOwner) {
 | 
					            onMoreCallback(data, 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()
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,6 +8,7 @@ import android.view.View
 | 
				
			|||||||
import android.view.ViewGroup
 | 
					import android.view.ViewGroup
 | 
				
			||||||
import android.view.inputmethod.InputMethodManager
 | 
					import android.view.inputmethod.InputMethodManager
 | 
				
			||||||
import android.widget.Toast
 | 
					import android.widget.Toast
 | 
				
			||||||
 | 
					import androidx.appcompat.app.AlertDialog
 | 
				
			||||||
import androidx.recyclerview.widget.LinearLayoutManager
 | 
					import androidx.recyclerview.widget.LinearLayoutManager
 | 
				
			||||||
import androidx.recyclerview.widget.RecyclerView
 | 
					import androidx.recyclerview.widget.RecyclerView
 | 
				
			||||||
import coil.load
 | 
					import coil.load
 | 
				
			||||||
@@ -140,20 +141,89 @@ class CharacterCommentReplyFragment : BaseFragment<FragmentCharacterCommentReply
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        adapter = CharacterCommentReplyAdapter(
 | 
					        adapter = CharacterCommentReplyAdapter(
 | 
				
			||||||
            currentUserId = SharedPreferenceManager.userId,
 | 
					            currentUserId = SharedPreferenceManager.userId,
 | 
				
			||||||
            onModify = { id, newText, isReply ->
 | 
					            onMore = { reply, isOwner ->
 | 
				
			||||||
                Toast.makeText(requireContext(), "수정 스텁: $id", Toast.LENGTH_SHORT).show()
 | 
					                CharacterCommentMoreBottomSheet.newInstance(isOwner).apply {
 | 
				
			||||||
            },
 | 
					                    onReport = {
 | 
				
			||||||
            onDelete = { id, isReply ->
 | 
					                        val reportSheet = CharacterCommentReportBottomSheet.newInstance()
 | 
				
			||||||
                val index = adapter.items.indexOfFirst {
 | 
					                        reportSheet.onSubmit = { reason ->
 | 
				
			||||||
                    when (it) {
 | 
					                            val token = "Bearer ${SharedPreferenceManager.token}"
 | 
				
			||||||
                        is CharacterReplyResponse -> it.replyId == id
 | 
					                            val d = repository.reportComment(
 | 
				
			||||||
                        else -> false
 | 
					                                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)
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                if (index > 0) { // 0은 원본 댓글이므로 제외
 | 
					                        reportSheet.show(parentFragmentManager, "reply_report")
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    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.items.removeAt(index)
 | 
				
			||||||
                                                adapter.notifyItemRemoved(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 {
 | 
					        ).apply {
 | 
				
			||||||
            items.clear()
 | 
					            items.clear()
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user