From df1746976c2b8034b0e274200d12fa434791cbe5 Mon Sep 17 00:00:00 2001 From: klaus Date: Tue, 19 Aug 2025 18:35:04 +0900 Subject: [PATCH] =?UTF-8?q?feat(character-detail):=20=EC=BA=90=EB=A6=AD?= =?UTF-8?q?=ED=84=B0=20=EC=83=81=EC=84=B8=20=EB=8C=93=EA=B8=80=20=EC=84=B9?= =?UTF-8?q?=EC=85=98=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EB=B0=94=EC=9D=B8=EB=94=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 댓글 입력 필드 stroke(흰색 1dp stroke와 radius 5dp) 추가 - 입력 박스 내부 우측에 전송 아이콘(ic_message_send) 추가 - 배경 드로어블(#263238, radius 10dp) 추가 - CharacterCommentResponse에 comment(nullable) 필드 추가 - CharacterDetailActivity에서 latestComment/totalComments 바인딩 및 UI 분기 처리 --- .../character/comment/CharacterCommentDto.kt | 52 +++++++ .../detail/CharacterDetailActivity.kt | 70 ++++++++- .../detail/CharacterDetailResponse.kt | 5 +- .../drawable/bg_round_corner_10_263238.xml | 6 + .../bg_round_corner_5_stroke_white.xml | 9 ++ .../res/layout/activity_character_detail.xml | 133 ++++++++++++++++++ 6 files changed, 272 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/chat/character/comment/CharacterCommentDto.kt create mode 100644 app/src/main/res/drawable/bg_round_corner_10_263238.xml create mode 100644 app/src/main/res/drawable/bg_round_corner_5_stroke_white.xml diff --git a/app/src/main/java/kr/co/vividnext/sodalive/chat/character/comment/CharacterCommentDto.kt b/app/src/main/java/kr/co/vividnext/sodalive/chat/character/comment/CharacterCommentDto.kt new file mode 100644 index 00000000..323d2001 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/chat/character/comment/CharacterCommentDto.kt @@ -0,0 +1,52 @@ +package kr.co.vividnext.sodalive.chat.character.comment + +import androidx.annotation.Keep +import com.google.gson.annotations.SerializedName + +// Request DTOs +@Keep +data class CreateCharacterCommentRequest( + @SerializedName("comment") val comment: String +) + +// Response DTOs +// 댓글 Response +// - 댓글 ID +// - 댓글 쓴 Member 프로필 이미지 +// - 댓글 쓴 Member 닉네임 +// - 댓글 쓴 시간 timestamp(long) +// - 답글 수 + +@Keep +data class CharacterCommentResponse( + @SerializedName("commentId") val commentId: Long, + @SerializedName("memberProfileImage") val memberProfileImage: String, + @SerializedName("memberNickname") val memberNickname: String, + @SerializedName("createdAt") val createdAt: Long, + @SerializedName("replyCount") val replyCount: Int, + @SerializedName("comment") val comment: String? +) + +// 답글 Response 단건(목록 원소) +// - 답글 ID +// - 답글 쓴 Member 프로필 이미지 +// - 답글 쓴 Member 닉네임 +// - 답글 쓴 시간 timestamp(long) + +@Keep +data class CharacterReplyResponse( + @SerializedName("replyId") val replyId: Long, + @SerializedName("memberProfileImage") val memberProfileImage: String, + @SerializedName("memberNickname") val memberNickname: String, + @SerializedName("createdAt") val createdAt: Long +) + +// 댓글의 답글 조회 Response 컨테이너 +// - 원본 댓글 Response +// - 답글 목록(위 사양의 필드 포함) + +@Keep +data class CharacterCommentRepliesResponse( + @SerializedName("original") val original: CharacterCommentResponse, + @SerializedName("replies") val replies: List +) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/chat/character/detail/CharacterDetailActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/chat/character/detail/CharacterDetailActivity.kt index 1602d2b2..6b3e8a57 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/chat/character/detail/CharacterDetailActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/chat/character/detail/CharacterDetailActivity.kt @@ -1,5 +1,6 @@ package kr.co.vividnext.sodalive.chat.character.detail +import android.annotation.SuppressLint import android.content.Intent import android.graphics.Rect import android.os.Bundle @@ -9,10 +10,12 @@ import androidx.core.net.toUri import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import coil.load +import coil.transform.CircleCropTransformation import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.base.BaseActivity import kr.co.vividnext.sodalive.chat.talk.room.ChatRoomActivity import kr.co.vividnext.sodalive.common.LoadingDialog +import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.databinding.ActivityCharacterDetailBinding import kr.co.vividnext.sodalive.extensions.dpToPx import org.koin.androidx.viewmodel.ext.android.viewModel @@ -119,6 +122,7 @@ class CharacterDetailActivity : BaseActivity( } } + @SuppressLint("SetTextI18n") private fun bindObservers() { viewModel.uiState.observe(this) { state -> // 1) 로딩 상태 처리 @@ -169,7 +173,8 @@ class CharacterDetailActivity : BaseActivity( binding.tvWorldviewContent.text = worldviewText // 먼저 전체 줄 수를 측정한 뒤 접힘 레이아웃 적용 binding.tvWorldviewContent.post { - val totalLines = binding.tvWorldviewContent.layout?.lineCount ?: binding.tvWorldviewContent.lineCount + val totalLines = binding.tvWorldviewContent.layout?.lineCount + ?: binding.tvWorldviewContent.lineCount val needExpand = totalLines > 3 binding.llWorldviewExpand.visibility = if (needExpand) View.VISIBLE else View.GONE // 표시 상태는 항상 접힘 상태로 시작 @@ -184,7 +189,8 @@ class CharacterDetailActivity : BaseActivity( binding.tvPersonalityContent.text = personalityText // 먼저 전체 줄 수를 측정한 뒤 접힘 레이아웃 적용 binding.tvPersonalityContent.post { - val totalLines = binding.tvPersonalityContent.layout?.lineCount ?: binding.tvPersonalityContent.lineCount + val totalLines = binding.tvPersonalityContent.layout?.lineCount + ?: binding.tvPersonalityContent.lineCount val needExpand = totalLines > 3 binding.llPersonalityExpand.visibility = if (needExpand) View.VISIBLE else View.GONE applyPersonalityCollapsedLayout() @@ -213,6 +219,66 @@ class CharacterDetailActivity : BaseActivity( binding.llOtherCharactersSection.visibility = View.VISIBLE adapter.submitList(detail.others) } + + // 댓글 섹션 바인딩 + binding.tvCommentsCount.text = "${detail.totalComments}" + if ( + detail.totalComments > 0 && + detail.latestComment != null && + detail.latestComment.comment.isNullOrBlank() + ) { + binding.llLatestComment.visibility = View.VISIBLE + binding.llNoComment.visibility = View.GONE + + val latest = detail.latestComment + val profileUrl = latest.memberProfileImage + if (profileUrl.isNotBlank()) { + binding.ivCommentProfile.load(profileUrl) { + crossfade(true) + placeholder(R.drawable.ic_placeholder_profile) + error(R.drawable.ic_placeholder_profile) + transformations(CircleCropTransformation()) + } + } else { + binding.ivMyProfile.load(R.drawable.ic_placeholder_profile) { + crossfade(true) + placeholder(R.drawable.ic_placeholder_profile) + error(R.drawable.ic_placeholder_profile) + transformations(CircleCropTransformation()) + } + } + + val commentText = latest.comment + binding.tvLatestComment.text = if (!commentText.isNullOrBlank()) { + commentText + } else { + latest.memberNickname + } + } else { + binding.llLatestComment.visibility = View.GONE + binding.llNoComment.visibility = View.VISIBLE + + // 내 프로필 이미지는 SharedPreference의 profileImage 사용 (fallback: placeholder) + val myProfileUrl = SharedPreferenceManager.profileImage + if (myProfileUrl.isNotBlank()) { + binding.ivMyProfile.load(myProfileUrl) { + crossfade(true) + placeholder(R.drawable.ic_placeholder_profile) + error(R.drawable.ic_placeholder_profile) + transformations(CircleCropTransformation()) + } + } else { + binding.ivMyProfile.load(R.drawable.ic_placeholder_profile) { + crossfade(true) + placeholder(R.drawable.ic_placeholder_profile) + error(R.drawable.ic_placeholder_profile) + transformations(CircleCropTransformation()) + } + } + + binding.ivSendComment.setOnClickListener { + } + } } } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/chat/character/detail/CharacterDetailResponse.kt b/app/src/main/java/kr/co/vividnext/sodalive/chat/character/detail/CharacterDetailResponse.kt index 71fb9804..672a2cba 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/chat/character/detail/CharacterDetailResponse.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/chat/character/detail/CharacterDetailResponse.kt @@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.chat.character.detail import androidx.annotation.Keep import com.google.gson.annotations.SerializedName +import kr.co.vividnext.sodalive.chat.character.comment.CharacterCommentResponse @Keep data class CharacterDetailResponse( @@ -16,7 +17,9 @@ data class CharacterDetailResponse( @SerializedName("originalTitle") val originalTitle: String?, @SerializedName("originalLink") val originalLink: String?, @SerializedName("characterType") val characterType: CharacterType, - @SerializedName("others") val others: List + @SerializedName("others") val others: List, + @SerializedName("latestComment") val latestComment: CharacterCommentResponse?, + @SerializedName("totalComments") val totalComments: Int ) @Keep diff --git a/app/src/main/res/drawable/bg_round_corner_10_263238.xml b/app/src/main/res/drawable/bg_round_corner_10_263238.xml new file mode 100644 index 00000000..7954e2d7 --- /dev/null +++ b/app/src/main/res/drawable/bg_round_corner_10_263238.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/app/src/main/res/drawable/bg_round_corner_5_stroke_white.xml b/app/src/main/res/drawable/bg_round_corner_5_stroke_white.xml new file mode 100644 index 00000000..4bfd826a --- /dev/null +++ b/app/src/main/res/drawable/bg_round_corner_5_stroke_white.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/app/src/main/res/layout/activity_character_detail.xml b/app/src/main/res/layout/activity_character_detail.xml index 5f07a4a3..a284d105 100644 --- a/app/src/main/res/layout/activity_character_detail.xml +++ b/app/src/main/res/layout/activity_character_detail.xml @@ -327,6 +327,139 @@ android:textSize="14sp" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +