feat(character-detail): 캐릭터 상세 댓글 섹션 추가 및 데이터 바인딩
- 댓글 입력 필드 stroke(흰색 1dp stroke와 radius 5dp) 추가 - 입력 박스 내부 우측에 전송 아이콘(ic_message_send) 추가 - 배경 드로어블(#263238, radius 10dp) 추가 - CharacterCommentResponse에 comment(nullable) 필드 추가 - CharacterDetailActivity에서 latestComment/totalComments 바인딩 및 UI 분기 처리
This commit is contained in:
@@ -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<CharacterReplyResponse>
|
||||
)
|
||||
@@ -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<ActivityCharacterDetailBinding>(
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun bindObservers() {
|
||||
viewModel.uiState.observe(this) { state ->
|
||||
// 1) 로딩 상태 처리
|
||||
@@ -169,7 +173,8 @@ class CharacterDetailActivity : BaseActivity<ActivityCharacterDetailBinding>(
|
||||
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<ActivityCharacterDetailBinding>(
|
||||
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<ActivityCharacterDetailBinding>(
|
||||
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 {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<OtherCharacter>
|
||||
@SerializedName("others") val others: List<OtherCharacter>,
|
||||
@SerializedName("latestComment") val latestComment: CharacterCommentResponse?,
|
||||
@SerializedName("totalComments") val totalComments: Int
|
||||
)
|
||||
|
||||
@Keep
|
||||
|
||||
Reference in New Issue
Block a user