feat(character-comment): 신고 BottomSheet 추가 및 삭제 확인 팝업 도입
- 신고 BottomSheet(제목/단일선택 리스트/신고 버튼) 구현 및 더보기→신고 흐름 연동 - 삭제 버튼 클릭 시 확인 다이얼로그 표시 후 확정 시 리스트에서 제거 - 신고/삭제 API 호출부는 스텁으로 남겨둠(후속 연동 예정)
This commit is contained in:
@@ -92,15 +92,30 @@ class CharacterCommentListFragment : BaseFragment<FragmentCharacterCommentListBi
|
|||||||
onClickMore = { item, isOwner, anchor ->
|
onClickMore = { item, isOwner, anchor ->
|
||||||
CharacterCommentMoreBottomSheet.newInstance(isOwner).apply {
|
CharacterCommentMoreBottomSheet.newInstance(isOwner).apply {
|
||||||
onReport = {
|
onReport = {
|
||||||
Toast.makeText(requireContext(), "신고되었습니다 (stub)", Toast.LENGTH_SHORT).show()
|
// 더보기 닫히고 신고 BottomSheet 열림
|
||||||
|
val reportSheet = CharacterCommentReportBottomSheet.newInstance()
|
||||||
|
reportSheet.onSubmit = { reason ->
|
||||||
|
// 신고 API 스텁 호출 지점
|
||||||
|
Toast.makeText(requireContext(), "신고 접수: $reason (stub)", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
reportSheet.show(childFragmentManager, "comment_report")
|
||||||
}
|
}
|
||||||
onDelete = {
|
onDelete = {
|
||||||
|
// 삭제 확인 팝업
|
||||||
|
androidx.appcompat.app.AlertDialog.Builder(requireContext())
|
||||||
|
.setTitle(getString(R.string.confirm_delete_title))
|
||||||
|
.setMessage(getString(R.string.confirm_delete_message))
|
||||||
|
.setPositiveButton(getString(R.string.confirm)) { _, _ ->
|
||||||
|
// 삭제 API 스텁 호출 지점
|
||||||
val index = adapter.items.indexOfFirst { it.commentId == item.commentId }
|
val index = adapter.items.indexOfFirst { it.commentId == item.commentId }
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
adapter.items.removeAt(index)
|
adapter.items.removeAt(index)
|
||||||
adapter.notifyItemRemoved(index)
|
adapter.notifyItemRemoved(index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.setNegativeButton(getString(R.string.cancel), null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
}.show(childFragmentManager, "comment_more")
|
}.show(childFragmentManager, "comment_more")
|
||||||
},
|
},
|
||||||
onClickItem = { item ->
|
onClickItem = { item ->
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
package kr.co.vividnext.sodalive.chat.character.comment
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.Button
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.os.bundleOf
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||||
|
import kr.co.vividnext.sodalive.R
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 댓글/답글 신고 BottomSheet (Stub)
|
||||||
|
* - 제목: 신고
|
||||||
|
* - 신고 이유 단일 선택 목록(String List 주입 가능, 미주입 시 기본 목록 사용)
|
||||||
|
* - 최하단 신고 버튼(선택 전 비활성화, 선택 후 활성화)
|
||||||
|
* - 신고 버튼 클릭 시 onSubmit(reason) 콜백 호출 후 닫기 (API 스텁 호출은 콜백 쪽에서 처리)
|
||||||
|
*/
|
||||||
|
class CharacterCommentReportBottomSheet : BottomSheetDialogFragment() {
|
||||||
|
|
||||||
|
var onSubmit: ((String) -> Unit)? = null
|
||||||
|
|
||||||
|
private var reasons: ArrayList<String>? = null
|
||||||
|
private var selectedIndex: Int = -1
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
reasons = arguments?.getStringArrayList(ARG_REASONS) ?: DEFAULT_REASONS
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
val view = inflater.inflate(R.layout.dialog_character_comment_report, container, false)
|
||||||
|
val tvTitle = view.findViewById<TextView>(R.id.tv_title)
|
||||||
|
val llList = view.findViewById<LinearLayout>(R.id.ll_reason_list)
|
||||||
|
val btnReport = view.findViewById<Button>(R.id.btn_report)
|
||||||
|
val ivClose = view.findViewById<ImageView>(R.id.iv_close)
|
||||||
|
|
||||||
|
tvTitle.text = getString(R.string.report_title)
|
||||||
|
btnReport.isEnabled = false
|
||||||
|
|
||||||
|
val items = reasons ?: DEFAULT_REASONS
|
||||||
|
|
||||||
|
// 동적 리스트 구성: 단일 선택
|
||||||
|
items.forEachIndexed { index, text ->
|
||||||
|
val itemView = inflater.inflate(R.layout.item_report_reason, llList, false)
|
||||||
|
val tvText = itemView.findViewById<TextView>(R.id.tv_reason)
|
||||||
|
val ivCheck = itemView.findViewById<ImageView>(R.id.iv_check)
|
||||||
|
tvText.text = text
|
||||||
|
ivCheck.isVisible = index == selectedIndex
|
||||||
|
itemView.setOnClickListener {
|
||||||
|
selectedIndex = index
|
||||||
|
// 전체를 다시 그릴 정도는 아니므로 자식들만 순회하며 체크 상태 갱신
|
||||||
|
for (i in 0 until llList.childCount) {
|
||||||
|
val child = llList.getChildAt(i)
|
||||||
|
val check = child.findViewById<ImageView>(R.id.iv_check)
|
||||||
|
check?.isVisible = i == selectedIndex
|
||||||
|
}
|
||||||
|
btnReport.isEnabled = true
|
||||||
|
}
|
||||||
|
llList.addView(itemView)
|
||||||
|
}
|
||||||
|
|
||||||
|
ivClose.setOnClickListener { dismiss() }
|
||||||
|
btnReport.setOnClickListener {
|
||||||
|
val idx = selectedIndex
|
||||||
|
if (idx in items.indices) {
|
||||||
|
onSubmit?.invoke(items[idx])
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val ARG_REASONS = "arg_reasons"
|
||||||
|
|
||||||
|
private val DEFAULT_REASONS = arrayListOf(
|
||||||
|
"스팸/광고", "욕설/비하", "음란물/불건전", "개인정보 노출", "기타"
|
||||||
|
)
|
||||||
|
|
||||||
|
fun newInstance(reasons: ArrayList<String>? = null): CharacterCommentReportBottomSheet {
|
||||||
|
return CharacterCommentReportBottomSheet().apply {
|
||||||
|
arguments = bundleOf(ARG_REASONS to reasons)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ import coil.load
|
|||||||
import coil.transform.CircleCropTransformation
|
import coil.transform.CircleCropTransformation
|
||||||
import kr.co.vividnext.sodalive.R
|
import kr.co.vividnext.sodalive.R
|
||||||
import kr.co.vividnext.sodalive.base.BaseActivity
|
import kr.co.vividnext.sodalive.base.BaseActivity
|
||||||
|
import kr.co.vividnext.sodalive.chat.character.comment.CharacterCommentListBottomSheet
|
||||||
import kr.co.vividnext.sodalive.chat.talk.room.ChatRoomActivity
|
import kr.co.vividnext.sodalive.chat.talk.room.ChatRoomActivity
|
||||||
import kr.co.vividnext.sodalive.common.LoadingDialog
|
import kr.co.vividnext.sodalive.common.LoadingDialog
|
||||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||||
@@ -226,14 +227,14 @@ class CharacterDetailActivity : BaseActivity<ActivityCharacterDetailBinding>(
|
|||||||
binding.llCommentsSection.setOnClickListener(null)
|
binding.llCommentsSection.setOnClickListener(null)
|
||||||
if (detail.totalComments > 0) {
|
if (detail.totalComments > 0) {
|
||||||
binding.llCommentsSection.setOnClickListener {
|
binding.llCommentsSection.setOnClickListener {
|
||||||
val sheet = kr.co.vividnext.sodalive.chat.character.comment.CharacterCommentListBottomSheet(detail.characterId)
|
val sheet = CharacterCommentListBottomSheet(detail.characterId)
|
||||||
sheet.show(supportFragmentManager, "character_comments")
|
sheet.show(supportFragmentManager, "character_comments")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
detail.totalComments > 0 &&
|
detail.totalComments > 0 &&
|
||||||
detail.latestComment != null &&
|
detail.latestComment != null &&
|
||||||
!detail.latestComment.comment.isNullOrBlank()
|
detail.latestComment.comment.isNotBlank()
|
||||||
) {
|
) {
|
||||||
binding.llLatestComment.visibility = View.VISIBLE
|
binding.llLatestComment.visibility = View.VISIBLE
|
||||||
binding.llNoComment.visibility = View.GONE
|
binding.llNoComment.visibility = View.GONE
|
||||||
@@ -256,10 +257,7 @@ class CharacterDetailActivity : BaseActivity<ActivityCharacterDetailBinding>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val commentText = latest.comment
|
binding.tvLatestComment.text = latest.comment.ifBlank {
|
||||||
binding.tvLatestComment.text = if (!commentText.isNullOrBlank()) {
|
|
||||||
commentText
|
|
||||||
} else {
|
|
||||||
latest.memberNickname
|
latest.memberNickname
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
55
app/src/main/res/layout/dialog_character_comment_report.xml
Normal file
55
app/src/main/res/layout/dialog_character_comment_report.xml
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/color_131313"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/report_title"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
tools:ignore="RelativeOverlap" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_close"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_circle_x_white" />
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:background="#78909C" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/ll_reason_list"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:orientation="vertical" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_report"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:backgroundTint="@color/color_3bb9f1"
|
||||||
|
android:enabled="false"
|
||||||
|
android:text="@string/report_button"
|
||||||
|
android:textColor="@color/white" />
|
||||||
|
</LinearLayout>
|
||||||
24
app/src/main/res/layout/item_report_reason.xml
Normal file
24
app/src/main/res/layout/item_report_reason.xml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingVertical="12dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_reason"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="15sp"
|
||||||
|
android:text=""/>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_check"
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@android:drawable/checkbox_on_background"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
</LinearLayout>
|
||||||
@@ -34,4 +34,12 @@
|
|||||||
<string name="status_sending">전송 중</string>
|
<string name="status_sending">전송 중</string>
|
||||||
<string name="status_failed">전송 실패</string>
|
<string name="status_failed">전송 실패</string>
|
||||||
<string name="status_sent">전송 완료</string>
|
<string name="status_sent">전송 완료</string>
|
||||||
|
|
||||||
|
<!-- Character comment report strings -->
|
||||||
|
<string name="report_title">신고</string>
|
||||||
|
<string name="report_button">신고</string>
|
||||||
|
<string name="confirm_delete_title">삭제</string>
|
||||||
|
<string name="confirm_delete_message">삭제 하시겠습니까?</string>
|
||||||
|
<string name="confirm">확인</string>
|
||||||
|
<string name="cancel">취소</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user