fix(character-detail): characterId 전달 및 상세 탭 전환 로직 수정
fix(character-detail): 탭 전환 시 프래그먼트 캐싱하여 재로딩 방지 CharacterDetailFragment에 newInstance(characterId) 도입 및 ARG 전달 구조 추가. Fragment에서 잘못된 intent 참조 제거하고 arguments → activity.intent 순으로 안전하게 조회. Activity 초기 진입 시 상세 탭 로딩 경로 정리 및 characterId 유효성 검사 시 종료 처리 보강. replace 기반 교체를 add/show/hide 구조로 전환. TAG_DETAIL/TAG_GALLERY로 인스턴스를 식별하여 FragmentManager 복원/재사용. 탭 이동 시 기존 인스턴스 표시만 수행하여 onViewCreated 재호출/네트워크 재요청 방지.
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
package kr.co.vividnext.sodalive.chat.character
|
package kr.co.vividnext.sodalive.chat.character
|
||||||
|
|
||||||
import io.reactivex.rxjava3.core.Single
|
import io.reactivex.rxjava3.core.Single
|
||||||
import kr.co.vividnext.sodalive.chat.character.detail.CharacterDetailResponse
|
import kr.co.vividnext.sodalive.chat.character.detail.detail.CharacterDetailResponse
|
||||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
import retrofit2.http.Header
|
import retrofit2.http.Header
|
||||||
|
|||||||
@@ -1,394 +1,96 @@
|
|||||||
package kr.co.vividnext.sodalive.chat.character.detail
|
package kr.co.vividnext.sodalive.chat.character.detail
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.Intent
|
|
||||||
import android.graphics.Rect
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.TextUtils
|
import androidx.fragment.app.Fragment
|
||||||
import android.view.View
|
import com.google.android.material.tabs.TabLayout
|
||||||
import androidx.core.net.toUri
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import coil.load
|
|
||||||
import coil.transform.CircleCropTransformation
|
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
|
||||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
|
||||||
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.character.detail.detail.CharacterDetailFragment
|
||||||
import kr.co.vividnext.sodalive.chat.character.comment.CharacterCommentRepository
|
import kr.co.vividnext.sodalive.chat.character.detail.gallery.CharacterGalleryFragment
|
||||||
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.databinding.ActivityCharacterDetailBinding
|
||||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
|
||||||
import org.koin.android.ext.android.inject
|
|
||||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
|
||||||
|
|
||||||
class CharacterDetailActivity : BaseActivity<ActivityCharacterDetailBinding>(
|
class CharacterDetailActivity : BaseActivity<ActivityCharacterDetailBinding>(
|
||||||
ActivityCharacterDetailBinding::inflate
|
ActivityCharacterDetailBinding::inflate
|
||||||
) {
|
) {
|
||||||
private val viewModel: CharacterDetailViewModel by viewModel()
|
|
||||||
private val commentRepository: CharacterCommentRepository by inject()
|
|
||||||
|
|
||||||
private lateinit var loadingDialog: LoadingDialog
|
|
||||||
|
|
||||||
private val adapter by lazy {
|
|
||||||
OtherCharacterAdapter(
|
|
||||||
onItemClick = { item ->
|
|
||||||
startActivity(
|
|
||||||
Intent(this, CharacterDetailActivity::class.java).apply {
|
|
||||||
putExtra(EXTRA_CHARACTER_ID, item.characterId)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private var isWorldviewExpanded = false
|
|
||||||
private var isPersonalityExpanded = false
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
// 더미 데이터 로드 (추후 Intent/Repository 연동)
|
|
||||||
val characterId = intent.getLongExtra(EXTRA_CHARACTER_ID, 0)
|
val characterId = intent.getLongExtra(EXTRA_CHARACTER_ID, 0)
|
||||||
|
|
||||||
if (characterId <= 0) {
|
if (characterId <= 0) {
|
||||||
showToast("잘못된 접근 입니다.")
|
showToast("잘못된 접근 입니다.")
|
||||||
finish()
|
finish()
|
||||||
} else {
|
return
|
||||||
bindObservers()
|
|
||||||
viewModel.load(characterId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setupView() {
|
override fun setupView() {
|
||||||
loadingDialog = LoadingDialog(this, layoutInflater)
|
|
||||||
|
|
||||||
// 뒤로 가기
|
// 뒤로 가기
|
||||||
binding.detailToolbar.tvBack.setOnClickListener { finish() }
|
binding.detailToolbar.tvBack.setOnClickListener { finish() }
|
||||||
|
|
||||||
// 탭 구성: 상세, 갤러리
|
// 탭 구성: 상세, 갤러리
|
||||||
binding.tabLayout.addTab(binding.tabLayout.newTab().setText("상세"))
|
binding.tabLayout.addTab(binding.tabLayout.newTab().setText("상세"))
|
||||||
binding.tabLayout.addTab(binding.tabLayout.newTab().setText("갤러리"))
|
binding.tabLayout.addTab(binding.tabLayout.newTab().setText("갤러리"))
|
||||||
binding.tabLayout.addOnTabSelectedListener(object :
|
|
||||||
com.google.android.material.tabs.TabLayout.OnTabSelectedListener {
|
val characterId = intent.getLongExtra(EXTRA_CHARACTER_ID, 0)
|
||||||
override fun onTabSelected(tab: com.google.android.material.tabs.TabLayout.Tab) {
|
|
||||||
if (tab.position == 0) {
|
// 기존 프래그먼트 복원/재사용
|
||||||
// 상세 탭: 기존 스크롤 화면 표시
|
var detail = supportFragmentManager.findFragmentByTag(TAG_DETAIL)
|
||||||
binding.scrollViewCharacterDetail.visibility = View.VISIBLE
|
var gallery = supportFragmentManager.findFragmentByTag(TAG_GALLERY)
|
||||||
binding.flContainer.visibility = View.GONE
|
|
||||||
} else {
|
val transaction = supportFragmentManager.beginTransaction()
|
||||||
// 갤러리 탭: 컨테이너 표시 및 갤러리 프래그먼트 로드
|
if (detail == null) {
|
||||||
binding.scrollViewCharacterDetail.visibility = View.GONE
|
detail = CharacterDetailFragment.newInstance(characterId)
|
||||||
binding.flContainer.visibility = View.VISIBLE
|
transaction.add(R.id.fl_container, detail, TAG_DETAIL)
|
||||||
val fragment =
|
|
||||||
kr.co.vividnext.sodalive.chat.character.detail.gallery.CharacterGalleryFragment()
|
|
||||||
supportFragmentManager.beginTransaction()
|
|
||||||
.replace(R.id.fl_container, fragment)
|
|
||||||
.commit()
|
|
||||||
}
|
}
|
||||||
|
if (gallery == null) {
|
||||||
|
gallery = CharacterGalleryFragment()
|
||||||
|
transaction.add(R.id.fl_container, gallery, TAG_GALLERY)
|
||||||
|
transaction.hide(gallery)
|
||||||
|
}
|
||||||
|
transaction.show(detail).commit()
|
||||||
|
|
||||||
|
binding.tabLayout
|
||||||
|
.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
|
||||||
|
override fun onTabSelected(tab: TabLayout.Tab) {
|
||||||
|
showTab(tab.position)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTabUnselected(tab: com.google.android.material.tabs.TabLayout.Tab) {}
|
override fun onTabUnselected(tab: TabLayout.Tab) {}
|
||||||
override fun onTabReselected(tab: com.google.android.material.tabs.TabLayout.Tab) {}
|
override fun onTabReselected(tab: TabLayout.Tab) {}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 다른 캐릭터 리스트: 가로 스크롤
|
|
||||||
val recyclerView = binding.rvOtherCharacters
|
|
||||||
recyclerView.layoutManager = LinearLayoutManager(
|
|
||||||
this,
|
|
||||||
LinearLayoutManager.HORIZONTAL,
|
|
||||||
false
|
|
||||||
)
|
|
||||||
|
|
||||||
recyclerView.addItemDecoration(object : RecyclerView.ItemDecoration() {
|
|
||||||
override fun getItemOffsets(
|
|
||||||
outRect: Rect,
|
|
||||||
view: View,
|
|
||||||
parent: RecyclerView,
|
|
||||||
state: RecyclerView.State
|
|
||||||
) {
|
|
||||||
super.getItemOffsets(outRect, view, parent, state)
|
|
||||||
|
|
||||||
when (parent.getChildAdapterPosition(view)) {
|
|
||||||
0 -> {
|
|
||||||
outRect.left = 0
|
|
||||||
outRect.right = 8f.dpToPx().toInt()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
adapter.itemCount - 1 -> {
|
private fun showTab(position: Int) {
|
||||||
outRect.left = 8f.dpToPx().toInt()
|
val detail = supportFragmentManager.findFragmentByTag(TAG_DETAIL)
|
||||||
outRect.right = 0
|
val gallery = supportFragmentManager.findFragmentByTag(TAG_GALLERY)
|
||||||
|
val transaction = supportFragmentManager.beginTransaction()
|
||||||
|
|
||||||
|
fun Fragment?.hideIfExists() {
|
||||||
|
if (this != null && !this.isHidden) transaction.hide(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
// 모두 숨김
|
||||||
outRect.left = 8f.dpToPx().toInt()
|
detail.hideIfExists()
|
||||||
outRect.right = 8f.dpToPx().toInt()
|
gallery.hideIfExists()
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
recyclerView.adapter = adapter
|
// 포지션에 맞게 표시
|
||||||
|
val toShow: Fragment? = when (position) {
|
||||||
// 세계관 전체보기 토글 클릭 리스너
|
0 -> detail
|
||||||
binding.llWorldviewExpand.setOnClickListener {
|
else -> gallery
|
||||||
toggleWorldviewExpand()
|
|
||||||
}
|
}
|
||||||
// 성격 전체보기 토글 클릭 리스너
|
if (toShow != null) transaction.show(toShow)
|
||||||
binding.llPersonalityExpand.setOnClickListener {
|
transaction.commit()
|
||||||
togglePersonalityExpand()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 대화하기 버튼 클릭: 채팅방 생성 API 호출
|
fun setTitle(title: String) {
|
||||||
binding.btnChat.setOnClickListener {
|
binding.detailToolbar.tvBack.text = title
|
||||||
val idFromState = viewModel.uiState.value?.detail?.characterId ?: 0L
|
|
||||||
val idFromIntent = intent.getLongExtra(EXTRA_CHARACTER_ID, 0L)
|
|
||||||
val targetId = if (idFromState > 0) idFromState else idFromIntent
|
|
||||||
if (targetId > 0) {
|
|
||||||
viewModel.createChatRoom(targetId)
|
|
||||||
} else {
|
|
||||||
showToast("잘못된 접근 입니다.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
|
||||||
private fun bindObservers() {
|
|
||||||
viewModel.uiState.observe(this) { state ->
|
|
||||||
// 1) 로딩 상태 처리
|
|
||||||
if (state.isLoading) {
|
|
||||||
loadingDialog.show(screenWidth)
|
|
||||||
} else {
|
|
||||||
loadingDialog.dismiss()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2) 에러 토스트 처리
|
|
||||||
state.error?.let { errorMsg ->
|
|
||||||
if (errorMsg.isNotBlank()) {
|
|
||||||
showToast(errorMsg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2-1) 채팅방 생성 성공 처리 (이벤트)
|
|
||||||
state.chatRoomId?.let { roomId ->
|
|
||||||
startActivity(ChatRoomActivity.newIntent(this, roomId))
|
|
||||||
viewModel.consumeChatRoomCreated()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3) 상세 데이터가 있을 경우에만 기존 UI 바인딩 수행
|
|
||||||
val detail = state.detail ?: return@observe
|
|
||||||
|
|
||||||
// 배경 이미지
|
|
||||||
binding.ivCharacterBackground.load(detail.imageUrl) { crossfade(true) }
|
|
||||||
|
|
||||||
// 기본 정보
|
|
||||||
binding.detailToolbar.tvBack.text = detail.name
|
|
||||||
binding.tvCharacterName.text = detail.name
|
|
||||||
binding.tvCharacterStatus.text = when (detail.characterType) {
|
|
||||||
CharacterType.CLONE -> "Clone"
|
|
||||||
CharacterType.CHARACTER -> "Character"
|
|
||||||
}
|
|
||||||
// 캐릭터 타입에 따른 배경 설정
|
|
||||||
binding.tvCharacterStatus.setBackgroundResource(
|
|
||||||
when (detail.characterType) {
|
|
||||||
CharacterType.CLONE -> R.drawable.bg_character_status_clone
|
|
||||||
CharacterType.CHARACTER -> R.drawable.bg_character_status_character
|
|
||||||
}
|
|
||||||
)
|
|
||||||
binding.tvCharacterDescription.text = detail.description
|
|
||||||
binding.tvCharacterTags.text = detail.tags
|
|
||||||
|
|
||||||
// 세계관 내용과 버튼 가시성 초기화
|
|
||||||
val worldviewText = detail.backgrounds?.description.orEmpty()
|
|
||||||
binding.tvWorldviewContent.text = worldviewText
|
|
||||||
// 먼저 전체 줄 수를 측정한 뒤 접힘 레이아웃 적용
|
|
||||||
binding.tvWorldviewContent.post {
|
|
||||||
val totalLines = binding.tvWorldviewContent.layout?.lineCount
|
|
||||||
?: binding.tvWorldviewContent.lineCount
|
|
||||||
val needExpand = totalLines > 3
|
|
||||||
binding.llWorldviewExpand.visibility = if (needExpand) View.VISIBLE else View.GONE
|
|
||||||
// 표시 상태는 항상 접힘 상태로 시작
|
|
||||||
applyWorldviewCollapsedLayout()
|
|
||||||
isWorldviewExpanded = false
|
|
||||||
binding.tvWorldviewExpand.text = "더보기"
|
|
||||||
binding.ivWorldviewExpand.setImageResource(R.drawable.ic_chevron_down)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 성격 내용과 버튼 가시성 초기화
|
|
||||||
val personalityText = detail.personalities?.description.orEmpty()
|
|
||||||
binding.tvPersonalityContent.text = personalityText
|
|
||||||
// 먼저 전체 줄 수를 측정한 뒤 접힘 레이아웃 적용
|
|
||||||
binding.tvPersonalityContent.post {
|
|
||||||
val totalLines = binding.tvPersonalityContent.layout?.lineCount
|
|
||||||
?: binding.tvPersonalityContent.lineCount
|
|
||||||
val needExpand = totalLines > 3
|
|
||||||
binding.llPersonalityExpand.visibility = if (needExpand) View.VISIBLE else View.GONE
|
|
||||||
applyPersonalityCollapsedLayout()
|
|
||||||
isPersonalityExpanded = false
|
|
||||||
binding.tvPersonalityExpand.text = "더보기"
|
|
||||||
binding.ivPersonalityExpand.setImageResource(R.drawable.ic_chevron_down)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 원작 섹션 표시/숨김
|
|
||||||
if (detail.originalTitle.isNullOrBlank() || detail.originalLink.isNullOrBlank()) {
|
|
||||||
binding.llOriginalSection.visibility = View.GONE
|
|
||||||
} else {
|
|
||||||
binding.llOriginalSection.visibility = View.VISIBLE
|
|
||||||
binding.tvOriginalContent.text = detail.originalTitle
|
|
||||||
binding.tvOriginalLink.setOnClickListener {
|
|
||||||
runCatching {
|
|
||||||
startActivity(Intent(Intent.ACTION_VIEW, detail.originalLink.toUri()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 다른 캐릭터 리스트
|
|
||||||
if (detail.others.isEmpty()) {
|
|
||||||
binding.llOtherCharactersSection.visibility = View.GONE
|
|
||||||
} else {
|
|
||||||
binding.llOtherCharactersSection.visibility = View.VISIBLE
|
|
||||||
adapter.submitList(detail.others)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 댓글 섹션 바인딩
|
|
||||||
binding.tvCommentsCount.text = "${detail.totalComments}"
|
|
||||||
// 댓글 섹션 터치 시 리스트 BottomSheet 열기 (댓글 1개 이상일 때)
|
|
||||||
binding.llCommentsSection.setOnClickListener(null)
|
|
||||||
if (detail.totalComments > 0) {
|
|
||||||
binding.llCommentsSection.setOnClickListener {
|
|
||||||
val sheet = CharacterCommentListBottomSheet(detail.characterId)
|
|
||||||
sheet.show(supportFragmentManager, "character_comments")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
detail.totalComments > 0 &&
|
|
||||||
detail.latestComment != null &&
|
|
||||||
detail.latestComment.comment.isNotBlank()
|
|
||||||
) {
|
|
||||||
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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.tvLatestComment.text = latest.comment.ifBlank {
|
|
||||||
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 {
|
|
||||||
val text = binding.etCommentInput.text?.toString()?.trim().orEmpty()
|
|
||||||
if (text.isBlank()) return@setOnClickListener
|
|
||||||
|
|
||||||
val idFromState = viewModel.uiState.value?.detail?.characterId ?: 0L
|
|
||||||
val idFromIntent = intent.getLongExtra(EXTRA_CHARACTER_ID, 0L)
|
|
||||||
val characterId = if (idFromState > 0) idFromState else idFromIntent
|
|
||||||
if (characterId <= 0) {
|
|
||||||
showToast("잘못된 접근 입니다.")
|
|
||||||
return@setOnClickListener
|
|
||||||
}
|
|
||||||
|
|
||||||
val token = "Bearer ${SharedPreferenceManager.token}"
|
|
||||||
loadingDialog.show(screenWidth)
|
|
||||||
val d = commentRepository.createComment(characterId, text, token)
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.doFinally { loadingDialog.dismiss() }
|
|
||||||
.subscribe({ resp ->
|
|
||||||
if (resp.success) {
|
|
||||||
binding.etCommentInput.setText("")
|
|
||||||
showToast("등록되었습니다.")
|
|
||||||
viewModel.load(characterId)
|
|
||||||
} else {
|
|
||||||
showToast(resp.message ?: "요청 중 오류가 발생했습니다")
|
|
||||||
}
|
|
||||||
}, { e ->
|
|
||||||
showToast(e.message ?: "요청 중 오류가 발생했습니다")
|
|
||||||
})
|
|
||||||
compositeDisposable.add(d)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun toggleWorldviewExpand() {
|
|
||||||
isWorldviewExpanded = !isWorldviewExpanded
|
|
||||||
if (isWorldviewExpanded) {
|
|
||||||
// 확장 상태
|
|
||||||
binding.tvWorldviewContent.maxLines = Integer.MAX_VALUE
|
|
||||||
binding.tvWorldviewContent.ellipsize = null
|
|
||||||
binding.tvWorldviewExpand.text = "간략히"
|
|
||||||
binding.ivWorldviewExpand.setImageResource(R.drawable.ic_chevron_up)
|
|
||||||
} else {
|
|
||||||
// 접힘 상태 (3줄)
|
|
||||||
applyWorldviewCollapsedLayout()
|
|
||||||
binding.tvWorldviewExpand.text = "더보기"
|
|
||||||
binding.ivWorldviewExpand.setImageResource(R.drawable.ic_chevron_down)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun applyWorldviewCollapsedLayout() {
|
|
||||||
binding.tvWorldviewContent.maxLines = 3
|
|
||||||
binding.tvWorldviewContent.ellipsize = TextUtils.TruncateAt.END
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun togglePersonalityExpand() {
|
|
||||||
isPersonalityExpanded = !isPersonalityExpanded
|
|
||||||
if (isPersonalityExpanded) {
|
|
||||||
binding.tvPersonalityContent.maxLines = Integer.MAX_VALUE
|
|
||||||
binding.tvPersonalityContent.ellipsize = null
|
|
||||||
binding.tvPersonalityExpand.text = "간략히"
|
|
||||||
binding.ivPersonalityExpand.setImageResource(R.drawable.ic_chevron_up)
|
|
||||||
} else {
|
|
||||||
applyPersonalityCollapsedLayout()
|
|
||||||
binding.tvPersonalityExpand.text = "더보기"
|
|
||||||
binding.ivPersonalityExpand.setImageResource(R.drawable.ic_chevron_down)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun applyPersonalityCollapsedLayout() {
|
|
||||||
binding.tvPersonalityContent.maxLines = 3
|
|
||||||
binding.tvPersonalityContent.ellipsize = TextUtils.TruncateAt.END
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val EXTRA_CHARACTER_ID = "extra_character_id"
|
const val EXTRA_CHARACTER_ID = "extra_character_id"
|
||||||
|
private const val TAG_DETAIL = "tag_character_detail"
|
||||||
|
private const val TAG_GALLERY = "tag_character_gallery"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,383 @@
|
|||||||
package kr.co.vividnext.sodalive.chat.character.detail.detail
|
package kr.co.vividnext.sodalive.chat.character.detail.detail
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Intent
|
||||||
|
import android.graphics.Rect
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.text.TextUtils
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import coil.load
|
||||||
|
import coil.transform.CircleCropTransformation
|
||||||
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
|
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||||
|
import kr.co.vividnext.sodalive.R
|
||||||
import kr.co.vividnext.sodalive.base.BaseFragment
|
import kr.co.vividnext.sodalive.base.BaseFragment
|
||||||
|
import kr.co.vividnext.sodalive.chat.character.comment.CharacterCommentListBottomSheet
|
||||||
|
import kr.co.vividnext.sodalive.chat.character.comment.CharacterCommentRepository
|
||||||
|
import kr.co.vividnext.sodalive.chat.character.detail.CharacterDetailActivity
|
||||||
|
import kr.co.vividnext.sodalive.chat.character.detail.CharacterDetailActivity.Companion.EXTRA_CHARACTER_ID
|
||||||
|
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.FragmentCharacterDetailBinding
|
import kr.co.vividnext.sodalive.databinding.FragmentCharacterDetailBinding
|
||||||
|
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||||
|
import org.koin.android.ext.android.inject
|
||||||
|
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 캐릭터 상세 - 상세 탭
|
* 캐릭터 상세 - 상세 탭
|
||||||
* TODO: 기존 CharacterDetailActivity UI 바인딩 로직을 이 Fragment로 점진적으로 이전합니다.
|
|
||||||
*/
|
*/
|
||||||
class CharacterDetailFragment : BaseFragment<FragmentCharacterDetailBinding>(
|
class CharacterDetailFragment : BaseFragment<FragmentCharacterDetailBinding>(
|
||||||
FragmentCharacterDetailBinding::inflate
|
FragmentCharacterDetailBinding::inflate
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val ARG_CHARACTER_ID = "arg_character_id"
|
||||||
|
|
||||||
|
fun newInstance(characterId: Long): CharacterDetailFragment =
|
||||||
|
CharacterDetailFragment().apply {
|
||||||
|
arguments = Bundle().apply { putLong(ARG_CHARACTER_ID, characterId) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val viewModel: CharacterDetailViewModel by viewModel()
|
||||||
|
private val commentRepository: CharacterCommentRepository by inject()
|
||||||
|
|
||||||
|
private lateinit var loadingDialog: LoadingDialog
|
||||||
|
|
||||||
|
private val characterId: Long by lazy {
|
||||||
|
arguments?.getLong(ARG_CHARACTER_ID)
|
||||||
|
?: requireActivity().intent.getLongExtra(EXTRA_CHARACTER_ID, 0L)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val adapter by lazy {
|
||||||
|
OtherCharacterAdapter(
|
||||||
|
onItemClick = { item ->
|
||||||
|
startActivity(
|
||||||
|
Intent(
|
||||||
|
requireActivity(),
|
||||||
|
CharacterDetailActivity::class.java
|
||||||
|
).apply {
|
||||||
|
putExtra(EXTRA_CHARACTER_ID, item.characterId)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var isWorldviewExpanded = false
|
||||||
|
private var isPersonalityExpanded = false
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
// 추후 상세 UI/로직 반영 예정
|
|
||||||
|
setupView()
|
||||||
|
bindObservers()
|
||||||
|
|
||||||
|
viewModel.load(characterId)
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
|
private fun bindObservers() {
|
||||||
|
viewModel.uiState.observe(viewLifecycleOwner) { state ->
|
||||||
|
// 1) 로딩 상태 처리
|
||||||
|
if (state.isLoading) {
|
||||||
|
loadingDialog.show(screenWidth)
|
||||||
|
} else {
|
||||||
|
loadingDialog.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) 에러 토스트 처리
|
||||||
|
state.error?.let { errorMsg ->
|
||||||
|
if (errorMsg.isNotBlank()) {
|
||||||
|
showToast(errorMsg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2-1) 채팅방 생성 성공 처리 (이벤트)
|
||||||
|
state.chatRoomId?.let { roomId ->
|
||||||
|
startActivity(
|
||||||
|
ChatRoomActivity.newIntent(
|
||||||
|
requireActivity(),
|
||||||
|
roomId
|
||||||
|
)
|
||||||
|
)
|
||||||
|
viewModel.consumeChatRoomCreated()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) 상세 데이터가 있을 경우에만 기존 UI 바인딩 수행
|
||||||
|
val detail = state.detail ?: return@observe
|
||||||
|
|
||||||
|
// 배경 이미지
|
||||||
|
binding.ivCharacterBackground.load(detail.imageUrl) { crossfade(true) }
|
||||||
|
|
||||||
|
// 기본 정보
|
||||||
|
(requireActivity() as CharacterDetailActivity).setTitle(detail.name)
|
||||||
|
binding.tvCharacterName.text = detail.name
|
||||||
|
binding.tvCharacterStatus.text = when (detail.characterType) {
|
||||||
|
CharacterType.CLONE -> "Clone"
|
||||||
|
CharacterType.CHARACTER -> "Character"
|
||||||
|
}
|
||||||
|
// 캐릭터 타입에 따른 배경 설정
|
||||||
|
binding.tvCharacterStatus.setBackgroundResource(
|
||||||
|
when (detail.characterType) {
|
||||||
|
CharacterType.CLONE -> R.drawable.bg_character_status_clone
|
||||||
|
CharacterType.CHARACTER -> R.drawable.bg_character_status_character
|
||||||
|
}
|
||||||
|
)
|
||||||
|
binding.tvCharacterDescription.text = detail.description
|
||||||
|
binding.tvCharacterTags.text = detail.tags
|
||||||
|
|
||||||
|
// 세계관 내용과 버튼 가시성 초기화
|
||||||
|
val worldviewText = detail.backgrounds?.description.orEmpty()
|
||||||
|
binding.tvWorldviewContent.text = worldviewText
|
||||||
|
// 먼저 전체 줄 수를 측정한 뒤 접힘 레이아웃 적용
|
||||||
|
binding.tvWorldviewContent.post {
|
||||||
|
val totalLines = binding.tvWorldviewContent.layout?.lineCount
|
||||||
|
?: binding.tvWorldviewContent.lineCount
|
||||||
|
val needExpand = totalLines > 3
|
||||||
|
binding.llWorldviewExpand.visibility = if (needExpand) View.VISIBLE else View.GONE
|
||||||
|
// 표시 상태는 항상 접힘 상태로 시작
|
||||||
|
applyWorldviewCollapsedLayout()
|
||||||
|
isWorldviewExpanded = false
|
||||||
|
binding.tvWorldviewExpand.text = "더보기"
|
||||||
|
binding.ivWorldviewExpand.setImageResource(R.drawable.ic_chevron_down)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 성격 내용과 버튼 가시성 초기화
|
||||||
|
val personalityText = detail.personalities?.description.orEmpty()
|
||||||
|
binding.tvPersonalityContent.text = personalityText
|
||||||
|
// 먼저 전체 줄 수를 측정한 뒤 접힘 레이아웃 적용
|
||||||
|
binding.tvPersonalityContent.post {
|
||||||
|
val totalLines = binding.tvPersonalityContent.layout?.lineCount
|
||||||
|
?: binding.tvPersonalityContent.lineCount
|
||||||
|
val needExpand = totalLines > 3
|
||||||
|
binding.llPersonalityExpand.visibility = if (needExpand) View.VISIBLE else View.GONE
|
||||||
|
applyPersonalityCollapsedLayout()
|
||||||
|
isPersonalityExpanded = false
|
||||||
|
binding.tvPersonalityExpand.text = "더보기"
|
||||||
|
binding.ivPersonalityExpand.setImageResource(R.drawable.ic_chevron_down)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 원작 섹션 표시/숨김
|
||||||
|
if (detail.originalTitle.isNullOrBlank() || detail.originalLink.isNullOrBlank()) {
|
||||||
|
binding.llOriginalSection.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
binding.llOriginalSection.visibility = View.VISIBLE
|
||||||
|
binding.tvOriginalContent.text = detail.originalTitle
|
||||||
|
binding.tvOriginalLink.setOnClickListener {
|
||||||
|
runCatching {
|
||||||
|
startActivity(Intent(Intent.ACTION_VIEW, detail.originalLink.toUri()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 다른 캐릭터 리스트
|
||||||
|
if (detail.others.isEmpty()) {
|
||||||
|
binding.llOtherCharactersSection.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
binding.llOtherCharactersSection.visibility = View.VISIBLE
|
||||||
|
adapter.submitList(detail.others)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 댓글 섹션 바인딩
|
||||||
|
binding.tvCommentsCount.text = "${detail.totalComments}"
|
||||||
|
// 댓글 섹션 터치 시 리스트 BottomSheet 열기 (댓글 1개 이상일 때)
|
||||||
|
binding.llCommentsSection.setOnClickListener(null)
|
||||||
|
if (detail.totalComments > 0) {
|
||||||
|
binding.llCommentsSection.setOnClickListener {
|
||||||
|
val sheet = CharacterCommentListBottomSheet(detail.characterId)
|
||||||
|
sheet.show(requireActivity().supportFragmentManager, "character_comments")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
detail.totalComments > 0 &&
|
||||||
|
detail.latestComment != null &&
|
||||||
|
detail.latestComment.comment.isNotBlank()
|
||||||
|
) {
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.tvLatestComment.text = latest.comment.ifBlank {
|
||||||
|
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 {
|
||||||
|
val text = binding.etCommentInput.text?.toString()?.trim().orEmpty()
|
||||||
|
if (text.isBlank()) return@setOnClickListener
|
||||||
|
|
||||||
|
val idFromState = viewModel.uiState.value?.detail?.characterId ?: 0L
|
||||||
|
val targetCharacterId = if (idFromState > 0) idFromState else characterId
|
||||||
|
if (targetCharacterId <= 0) {
|
||||||
|
showToast("잘못된 접근 입니다.")
|
||||||
|
return@setOnClickListener
|
||||||
|
}
|
||||||
|
|
||||||
|
val token = "Bearer ${SharedPreferenceManager.token}"
|
||||||
|
loadingDialog.show(screenWidth)
|
||||||
|
val d = commentRepository.createComment(targetCharacterId, text, token)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.doFinally { loadingDialog.dismiss() }
|
||||||
|
.subscribe({ resp ->
|
||||||
|
if (resp.success) {
|
||||||
|
binding.etCommentInput.setText("")
|
||||||
|
showToast("등록되었습니다.")
|
||||||
|
viewModel.load(targetCharacterId)
|
||||||
|
} else {
|
||||||
|
showToast(resp.message ?: "요청 중 오류가 발생했습니다")
|
||||||
|
}
|
||||||
|
}, { e ->
|
||||||
|
showToast(e.message ?: "요청 중 오류가 발생했습니다")
|
||||||
|
})
|
||||||
|
compositeDisposable.add(d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupView() {
|
||||||
|
loadingDialog = LoadingDialog(requireActivity(), layoutInflater)
|
||||||
|
|
||||||
|
// 다른 캐릭터 리스트: 가로 스크롤
|
||||||
|
val recyclerView = binding.rvOtherCharacters
|
||||||
|
recyclerView.layoutManager = LinearLayoutManager(
|
||||||
|
requireContext(),
|
||||||
|
LinearLayoutManager.HORIZONTAL,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
|
||||||
|
recyclerView.addItemDecoration(object : RecyclerView.ItemDecoration() {
|
||||||
|
override fun getItemOffsets(
|
||||||
|
outRect: Rect,
|
||||||
|
view: View,
|
||||||
|
parent: RecyclerView,
|
||||||
|
state: RecyclerView.State
|
||||||
|
) {
|
||||||
|
super.getItemOffsets(outRect, view, parent, state)
|
||||||
|
|
||||||
|
when (parent.getChildAdapterPosition(view)) {
|
||||||
|
0 -> {
|
||||||
|
outRect.left = 0
|
||||||
|
outRect.right = 8f.dpToPx().toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
adapter.itemCount - 1 -> {
|
||||||
|
outRect.left = 8f.dpToPx().toInt()
|
||||||
|
outRect.right = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
outRect.left = 8f.dpToPx().toInt()
|
||||||
|
outRect.right = 8f.dpToPx().toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
recyclerView.adapter = adapter
|
||||||
|
|
||||||
|
// 세계관 전체보기 토글 클릭 리스너
|
||||||
|
binding.llWorldviewExpand.setOnClickListener {
|
||||||
|
toggleWorldviewExpand()
|
||||||
|
}
|
||||||
|
// 성격 전체보기 토글 클릭 리스너
|
||||||
|
binding.llPersonalityExpand.setOnClickListener {
|
||||||
|
togglePersonalityExpand()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 대화하기 버튼 클릭: 채팅방 생성 API 호출
|
||||||
|
binding.btnChat.setOnClickListener {
|
||||||
|
val idFromState = viewModel.uiState.value?.detail?.characterId ?: 0L
|
||||||
|
val targetId = if (idFromState > 0) idFromState else characterId
|
||||||
|
if (targetId > 0) {
|
||||||
|
viewModel.createChatRoom(targetId)
|
||||||
|
} else {
|
||||||
|
showToast("잘못된 접근 입니다.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun toggleWorldviewExpand() {
|
||||||
|
isWorldviewExpanded = !isWorldviewExpanded
|
||||||
|
if (isWorldviewExpanded) {
|
||||||
|
// 확장 상태
|
||||||
|
binding.tvWorldviewContent.maxLines = Integer.MAX_VALUE
|
||||||
|
binding.tvWorldviewContent.ellipsize = null
|
||||||
|
binding.tvWorldviewExpand.text = "간략히"
|
||||||
|
binding.ivWorldviewExpand.setImageResource(R.drawable.ic_chevron_up)
|
||||||
|
} else {
|
||||||
|
// 접힘 상태 (3줄)
|
||||||
|
applyWorldviewCollapsedLayout()
|
||||||
|
binding.tvWorldviewExpand.text = "더보기"
|
||||||
|
binding.ivWorldviewExpand.setImageResource(R.drawable.ic_chevron_down)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun applyWorldviewCollapsedLayout() {
|
||||||
|
binding.tvWorldviewContent.maxLines = 3
|
||||||
|
binding.tvWorldviewContent.ellipsize = TextUtils.TruncateAt.END
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun togglePersonalityExpand() {
|
||||||
|
isPersonalityExpanded = !isPersonalityExpanded
|
||||||
|
if (isPersonalityExpanded) {
|
||||||
|
binding.tvPersonalityContent.maxLines = Integer.MAX_VALUE
|
||||||
|
binding.tvPersonalityContent.ellipsize = null
|
||||||
|
binding.tvPersonalityExpand.text = "간략히"
|
||||||
|
binding.ivPersonalityExpand.setImageResource(R.drawable.ic_chevron_up)
|
||||||
|
} else {
|
||||||
|
applyPersonalityCollapsedLayout()
|
||||||
|
binding.tvPersonalityExpand.text = "더보기"
|
||||||
|
binding.ivPersonalityExpand.setImageResource(R.drawable.ic_chevron_down)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun applyPersonalityCollapsedLayout() {
|
||||||
|
binding.tvPersonalityContent.maxLines = 3
|
||||||
|
binding.tvPersonalityContent.ellipsize = TextUtils.TruncateAt.END
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package kr.co.vividnext.sodalive.chat.character.detail
|
package kr.co.vividnext.sodalive.chat.character.detail.detail
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.chat.character.CharacterApi
|
import kr.co.vividnext.sodalive.chat.character.CharacterApi
|
||||||
import kr.co.vividnext.sodalive.chat.talk.TalkApi
|
import kr.co.vividnext.sodalive.chat.talk.TalkApi
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package kr.co.vividnext.sodalive.chat.character.detail
|
package kr.co.vividnext.sodalive.chat.character.detail.detail
|
||||||
|
|
||||||
import androidx.annotation.Keep
|
import androidx.annotation.Keep
|
||||||
import com.google.gson.annotations.SerializedName
|
import com.google.gson.annotations.SerializedName
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package kr.co.vividnext.sodalive.chat.character.detail
|
package kr.co.vividnext.sodalive.chat.character.detail.detail
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package kr.co.vividnext.sodalive.chat.character.detail
|
package kr.co.vividnext.sodalive.chat.character.detail.detail
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
@@ -5,7 +5,7 @@ package kr.co.vividnext.sodalive.chat.talk.room
|
|||||||
|
|
||||||
import androidx.annotation.Keep
|
import androidx.annotation.Keep
|
||||||
import com.google.gson.annotations.SerializedName
|
import com.google.gson.annotations.SerializedName
|
||||||
import kr.co.vividnext.sodalive.chat.character.detail.CharacterType
|
import kr.co.vividnext.sodalive.chat.character.detail.detail.CharacterType
|
||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
data class CharacterInfo(
|
data class CharacterInfo(
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import androidx.recyclerview.widget.RecyclerView
|
|||||||
import coil.load
|
import coil.load
|
||||||
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.detail.CharacterType
|
import kr.co.vividnext.sodalive.chat.character.detail.detail.CharacterType
|
||||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||||
import kr.co.vividnext.sodalive.databinding.ActivityChatRoomBinding
|
import kr.co.vividnext.sodalive.databinding.ActivityChatRoomBinding
|
||||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||||
|
|||||||
@@ -69,8 +69,8 @@ import kr.co.vividnext.sodalive.chat.character.CharacterTabRepository
|
|||||||
import kr.co.vividnext.sodalive.chat.character.CharacterTabViewModel
|
import kr.co.vividnext.sodalive.chat.character.CharacterTabViewModel
|
||||||
import kr.co.vividnext.sodalive.chat.character.comment.CharacterCommentApi
|
import kr.co.vividnext.sodalive.chat.character.comment.CharacterCommentApi
|
||||||
import kr.co.vividnext.sodalive.chat.character.comment.CharacterCommentRepository
|
import kr.co.vividnext.sodalive.chat.character.comment.CharacterCommentRepository
|
||||||
import kr.co.vividnext.sodalive.chat.character.detail.CharacterDetailRepository
|
import kr.co.vividnext.sodalive.chat.character.detail.detail.CharacterDetailRepository
|
||||||
import kr.co.vividnext.sodalive.chat.character.detail.CharacterDetailViewModel
|
import kr.co.vividnext.sodalive.chat.character.detail.detail.CharacterDetailViewModel
|
||||||
import kr.co.vividnext.sodalive.chat.talk.TalkApi
|
import kr.co.vividnext.sodalive.chat.talk.TalkApi
|
||||||
import kr.co.vividnext.sodalive.chat.talk.TalkTabRepository
|
import kr.co.vividnext.sodalive.chat.talk.TalkTabRepository
|
||||||
import kr.co.vividnext.sodalive.chat.talk.TalkTabViewModel
|
import kr.co.vividnext.sodalive.chat.talk.TalkTabViewModel
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="@color/color_131313">
|
android:background="@color/color_131313">
|
||||||
@@ -9,14 +8,21 @@
|
|||||||
<!-- 상단 툴바 -->
|
<!-- 상단 툴바 -->
|
||||||
<include
|
<include
|
||||||
android:id="@+id/detail_toolbar"
|
android:id="@+id/detail_toolbar"
|
||||||
layout="@layout/detail_toolbar" />
|
layout="@layout/detail_toolbar"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<com.google.android.material.tabs.TabLayout
|
<com.google.android.material.tabs.TabLayout
|
||||||
android:id="@+id/tab_layout"
|
android:id="@+id/tab_layout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@+id/detail_toolbar"
|
|
||||||
android:background="@color/color_131313"
|
android:background="@color/color_131313"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/detail_toolbar"
|
||||||
app:tabIndicatorColor="@color/color_3bb9f1"
|
app:tabIndicatorColor="@color/color_3bb9f1"
|
||||||
app:tabIndicatorFullWidth="true"
|
app:tabIndicatorFullWidth="true"
|
||||||
app:tabIndicatorHeight="4dp"
|
app:tabIndicatorHeight="4dp"
|
||||||
@@ -24,515 +30,14 @@
|
|||||||
app:tabTextAppearance="@style/tabText"
|
app:tabTextAppearance="@style/tabText"
|
||||||
app:tabTextColor="@color/color_b0bec5" />
|
app:tabTextColor="@color/color_b0bec5" />
|
||||||
|
|
||||||
<!-- 메인 스크롤 영역 (상세 탭에서만 표시) -->
|
|
||||||
<androidx.core.widget.NestedScrollView
|
|
||||||
android:id="@+id/scroll_view_character_detail"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_above="@+id/btn_chat"
|
|
||||||
android:layout_below="@+id/tab_layout"
|
|
||||||
android:clipToPadding="false"
|
|
||||||
android:fillViewport="true">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<!-- 캐릭터 이미지 및 프로필 영역 -->
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
android:id="@+id/rl_character_profile"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="16dp">
|
|
||||||
|
|
||||||
<!-- 캐릭터 배경 이미지 -->
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/iv_character_background"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:contentDescription="@null"
|
|
||||||
android:scaleType="centerCrop"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintDimensionRatio="1:1"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
|
|
||||||
<!-- 캐릭터 정보 -->
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/ll_character_info"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginHorizontal="24dp"
|
|
||||||
android:layout_marginBottom="16dp"
|
|
||||||
android:orientation="vertical"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent">
|
|
||||||
|
|
||||||
<!-- 캐릭터명과 상태 -->
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/ll_character_name_status"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tv_character_name"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:fontFamily="@font/pretendard_bold"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textSize="26sp"
|
|
||||||
tools:text="캐릭터명" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tv_character_status"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:background="@drawable/bg_character_status_clone"
|
|
||||||
android:fontFamily="@font/pretendard_regular"
|
|
||||||
android:paddingHorizontal="5dp"
|
|
||||||
android:paddingVertical="1dp"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textSize="12sp"
|
|
||||||
tools:text="Clone" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<!-- 캐릭터 소개 -->
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tv_character_description"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:fontFamily="@font/pretendard_regular"
|
|
||||||
android:textColor="@color/color_b0bec5"
|
|
||||||
android:textSize="18sp"
|
|
||||||
tools:text="캐릭터 한줄 소개" />
|
|
||||||
|
|
||||||
<!-- 태그 -->
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tv_character_tags"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:fontFamily="@font/pretendard_regular"
|
|
||||||
android:textColor="@color/color_3bb9f1"
|
|
||||||
android:textSize="14sp"
|
|
||||||
tools:text="#커버곡 #라이브 #연애 #썸 #채팅 #라방" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<!-- 세계관 섹션 -->
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/ll_worldview_section"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginHorizontal="24dp"
|
|
||||||
android:layout_marginBottom="16dp"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<!-- 섹션 제목 -->
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tv_worldview_title"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:fontFamily="@font/pretendard_bold"
|
|
||||||
android:text="[세계관 및 작품 소개]"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textSize="16sp" />
|
|
||||||
|
|
||||||
<!-- 세계관 내용 -->
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tv_worldview_content"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:fontFamily="@font/pretendard_regular"
|
|
||||||
android:lineSpacingExtra="4dp"
|
|
||||||
android:textColor="@color/color_b0bec5"
|
|
||||||
android:textSize="16sp"
|
|
||||||
tools:text="특별한 꽃을 길러낼 수 있는 능력을 가진 리엘라.\n\n그녀는 호손 공작의 상속자가 되고 말아버리는데..." />
|
|
||||||
|
|
||||||
<!-- 더보기 버튼 -->
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/ll_worldview_expand"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
tools:ignore="UseCompoundDrawables">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/iv_worldview_expand"
|
|
||||||
android:layout_width="20dp"
|
|
||||||
android:layout_height="20dp"
|
|
||||||
android:contentDescription="@null"
|
|
||||||
android:src="@drawable/ic_chevron_down" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tv_worldview_expand"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:fontFamily="@font/pretendard_regular"
|
|
||||||
android:text="더보기"
|
|
||||||
android:textColor="@color/color_607d8b"
|
|
||||||
android:textSize="14sp" />
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<!-- 원작 섹션 -->
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/ll_original_section"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginHorizontal="36dp"
|
|
||||||
android:layout_marginBottom="16dp"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<!-- 섹션 제목 -->
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tv_original_title"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:fontFamily="@font/pretendard_bold"
|
|
||||||
android:text="[원작]"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textSize="16sp" />
|
|
||||||
|
|
||||||
<!-- 원작 내용 -->
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tv_original_content"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:ellipsize="end"
|
|
||||||
android:fontFamily="@font/pretendard_regular"
|
|
||||||
android:maxLines="3"
|
|
||||||
android:textColor="@color/color_b0bec5"
|
|
||||||
android:textSize="16sp"
|
|
||||||
tools:text="네이버 시리즈 독 안에 든 선생님" />
|
|
||||||
|
|
||||||
<!-- 원작 보러가기 버튼 -->
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tv_original_link"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="54dp"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:background="@drawable/bg_round_corner_16_stroke_3bb9f1"
|
|
||||||
android:fontFamily="@font/pretendard_bold"
|
|
||||||
android:gravity="center"
|
|
||||||
android:text="원작 보러가기"
|
|
||||||
android:textColor="@color/color_3bb9f1"
|
|
||||||
android:textSize="16sp" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<!-- 성격 섹션 (세계관과 동일 UI) -->
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/ll_personality_section"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginHorizontal="24dp"
|
|
||||||
android:layout_marginBottom="16dp"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<!-- 섹션 제목 -->
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tv_personality_title"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:fontFamily="@font/pretendard_bold"
|
|
||||||
android:text="[성격 및 특징]"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textSize="16sp" />
|
|
||||||
|
|
||||||
<!-- 성격 내용 -->
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tv_personality_content"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:fontFamily="@font/pretendard_regular"
|
|
||||||
android:lineSpacingExtra="4dp"
|
|
||||||
android:textColor="@color/color_b0bec5"
|
|
||||||
android:textSize="16sp"
|
|
||||||
tools:text="밝고 쾌활하지만 때로는 고집이 센 면모도 있습니다.\n\n친구를 소중히 여기며, 어려움 앞에서도 물러서지 않습니다." />
|
|
||||||
|
|
||||||
<!-- 더보기 버튼 -->
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/ll_personality_expand"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
tools:ignore="UseCompoundDrawables">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/iv_personality_expand"
|
|
||||||
android:layout_width="20dp"
|
|
||||||
android:layout_height="20dp"
|
|
||||||
android:contentDescription="@null"
|
|
||||||
android:src="@drawable/ic_chevron_down" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tv_personality_expand"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:fontFamily="@font/pretendard_regular"
|
|
||||||
android:text="더보기"
|
|
||||||
android:textColor="@color/color_607d8b"
|
|
||||||
android:textSize="14sp" />
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<!-- 캐릭터톡 대화 가이드 섹션 -->
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/ll_chat_guide_section"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginHorizontal="24dp"
|
|
||||||
android:layout_marginBottom="24dp"
|
|
||||||
android:background="@drawable/bg_round_corner_16_stroke_37474f"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:paddingHorizontal="16dp"
|
|
||||||
android:paddingVertical="12dp">
|
|
||||||
|
|
||||||
<!-- 가이드 제목 -->
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tv_chat_guide_title"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:fontFamily="@font/pretendard_bold"
|
|
||||||
android:text="⚠️ 캐릭터톡 대화 가이드"
|
|
||||||
android:textColor="@color/color_b0bec5"
|
|
||||||
android:textSize="16sp" />
|
|
||||||
|
|
||||||
<!-- 가이드 내용 -->
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tv_chat_guide_content"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:fontFamily="@font/pretendard_regular"
|
|
||||||
android:lineSpacingExtra="4dp"
|
|
||||||
android:text="보이스온 AI캐릭터톡은 대화의 자유도가 높아 대화에 참여하는 당신은 누구든 될 수 있습니다.\n세계관 속 캐릭터로 대화를 하거나 새로운 인물로 캐릭터와 당신만의 스토리를 만들어보세요."
|
|
||||||
android:textColor="@color/color_7c7c80"
|
|
||||||
android:textSize="16sp" />
|
|
||||||
|
|
||||||
<!-- 주의사항 -->
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tv_chat_guide_notice"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:fontFamily="@font/pretendard_regular"
|
|
||||||
android:lineSpacingExtra="4dp"
|
|
||||||
android:text="※ AI캐릭터톡은 오픈베타 서비스 중이며, 캐릭터의 대화가 어색하거나 불완전할 수 있습니다. 대화 초반에 캐릭터 붕괴가 느껴진다면 대화를 리셋하고 다시 시도해보세요."
|
|
||||||
android:textColor="@color/color_7c7c80"
|
|
||||||
android:textSize="14sp" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
|
|
||||||
<!-- 댓글 섹션 -->
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/ll_comments_section"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginHorizontal="24dp"
|
|
||||||
android:layout_marginBottom="24dp"
|
|
||||||
android:background="@drawable/bg_round_corner_10_263238"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:paddingHorizontal="16dp"
|
|
||||||
android:paddingVertical="12dp">
|
|
||||||
|
|
||||||
<!-- 헤더: 댓글 (댓글 수) -->
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tv_comments_label"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:fontFamily="@font/pretendard_regular"
|
|
||||||
android:text="댓글"
|
|
||||||
android:textColor="@color/color_b0bec5"
|
|
||||||
android:textSize="16sp" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tv_comments_count"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:fontFamily="@font/pretendard_bold"
|
|
||||||
android:textColor="@color/color_b0bec5"
|
|
||||||
android:textSize="16sp"
|
|
||||||
tools:text="0" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<!-- 내용 컨테이너 -->
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/ll_comments_container"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="10dp"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<!-- 댓글 있을 때 -->
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/ll_latest_comment"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:visibility="gone"
|
|
||||||
tools:ignore="UseCompoundDrawables">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/iv_comment_profile"
|
|
||||||
android:layout_width="36dp"
|
|
||||||
android:layout_height="36dp"
|
|
||||||
android:contentDescription="@null"
|
|
||||||
android:src="@drawable/ic_placeholder_profile" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tv_latest_comment"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="12dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:fontFamily="@font/pretendard_regular"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textSize="16sp"
|
|
||||||
tools:text="가장 최근 댓글 내용이 여기에 표시됩니다." />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<!-- 댓글 없을 때 -->
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/ll_no_comment"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/iv_my_profile"
|
|
||||||
android:layout_width="36dp"
|
|
||||||
android:layout_height="36dp"
|
|
||||||
android:contentDescription="@null"
|
|
||||||
android:src="@drawable/ic_placeholder_profile" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/ll_comment_input_box"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="12dp"
|
|
||||||
android:background="@drawable/bg_round_corner_5_stroke_white"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:paddingHorizontal="12dp"
|
|
||||||
android:paddingVertical="8dp">
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/et_comment_input"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="4dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:background="@android:color/transparent"
|
|
||||||
android:fontFamily="@font/pretendard_regular"
|
|
||||||
android:hint="댓글을 입력해보세요"
|
|
||||||
android:imeOptions="actionSend"
|
|
||||||
android:importantForAutofill="no"
|
|
||||||
android:inputType="textCapSentences|textMultiLine"
|
|
||||||
android:maxLines="3"
|
|
||||||
android:padding="0dp"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textColorHint="@color/color_7c7c80"
|
|
||||||
android:textSize="14sp"
|
|
||||||
tools:ignore="NestedWeights" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/iv_send_comment"
|
|
||||||
android:layout_width="24dp"
|
|
||||||
android:layout_height="24dp"
|
|
||||||
android:contentDescription="@null"
|
|
||||||
android:src="@drawable/ic_message_send" />
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<!-- 장르의 다른 캐릭터 섹션 -->
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/ll_other_characters_section"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="24dp"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<!-- 섹션 제목 -->
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:paddingHorizontal="24dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tv_other_characters_title"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:fontFamily="@font/pretendard_bold"
|
|
||||||
android:text="장르의 다른 캐릭터"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textSize="26sp" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<!-- 캐릭터 리스트 -->
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/rv_other_characters"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:clipToPadding="false"
|
|
||||||
android:paddingHorizontal="24dp" />
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
|
||||||
|
|
||||||
<!-- 갤러리 탭 컨테이너 -->
|
<!-- 갤러리 탭 컨테이너 -->
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/fl_container"
|
android:id="@+id/fl_container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="0dp"
|
||||||
android:layout_above="@+id/btn_chat"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
android:layout_below="@+id/tab_layout"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
android:visibility="gone" />
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/tab_layout" />
|
||||||
|
|
||||||
<!-- 하단 고정 대화하기 버튼 -->
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
<TextView
|
|
||||||
android:id="@+id/btn_chat"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="54dp"
|
|
||||||
android:layout_alignParentBottom="true"
|
|
||||||
android:layout_marginHorizontal="24dp"
|
|
||||||
android:layout_marginBottom="10dp"
|
|
||||||
android:background="@drawable/bg_round_corner_16_solid_3bb9f1"
|
|
||||||
android:fontFamily="@font/pretendard_bold"
|
|
||||||
android:gravity="center"
|
|
||||||
android:text="대화하기"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textSize="16sp" />
|
|
||||||
</RelativeLayout>
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="51.7dp"
|
android:layout_height="51.7dp"
|
||||||
android:background="@color/black"
|
android:background="@color/color_131313"
|
||||||
android:paddingHorizontal="13.3dp">
|
android:paddingHorizontal="13.3dp">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@@ -13,9 +13,9 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerVertical="true"
|
android:layout_centerVertical="true"
|
||||||
android:drawablePadding="6.7dp"
|
android:drawablePadding="6.7dp"
|
||||||
|
android:ellipsize="end"
|
||||||
android:fontFamily="@font/gmarket_sans_bold"
|
android:fontFamily="@font/gmarket_sans_bold"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:ellipsize="end"
|
|
||||||
android:minHeight="48dp"
|
android:minHeight="48dp"
|
||||||
android:textColor="@color/color_eeeeee"
|
android:textColor="@color/color_eeeeee"
|
||||||
android:textSize="18.3sp"
|
android:textSize="18.3sp"
|
||||||
|
|||||||
@@ -1,9 +1,518 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="@color/color_131313">
|
android:background="@color/color_131313">
|
||||||
|
|
||||||
<!-- TODO: 기존 상세 화면 UI를 이 레이아웃으로 이전 예정 -->
|
<!-- 메인 스크롤 영역 (상세 탭에서만 표시) -->
|
||||||
|
<androidx.core.widget.NestedScrollView
|
||||||
|
android:id="@+id/scroll_view_character_detail"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_above="@+id/btn_chat"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:fillViewport="true"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/btn_chat"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
</FrameLayout>
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="24dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<!-- 캐릭터 이미지 및 프로필 영역 -->
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/rl_character_profile"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="16dp">
|
||||||
|
|
||||||
|
<!-- 캐릭터 배경 이미지 -->
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_character_background"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintDimensionRatio="1:1"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<!-- 캐릭터 정보 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/ll_character_info"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="24dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent">
|
||||||
|
|
||||||
|
<!-- 캐릭터명과 상태 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/ll_character_name_status"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_character_name"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="@font/pretendard_bold"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="26sp"
|
||||||
|
tools:text="캐릭터명" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_character_status"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:background="@drawable/bg_character_status_clone"
|
||||||
|
android:fontFamily="@font/pretendard_regular"
|
||||||
|
android:paddingHorizontal="5dp"
|
||||||
|
android:paddingVertical="1dp"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="12sp"
|
||||||
|
tools:text="Clone" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 캐릭터 소개 -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_character_description"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:fontFamily="@font/pretendard_regular"
|
||||||
|
android:textColor="@color/color_b0bec5"
|
||||||
|
android:textSize="18sp"
|
||||||
|
tools:text="캐릭터 한줄 소개" />
|
||||||
|
|
||||||
|
<!-- 태그 -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_character_tags"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:fontFamily="@font/pretendard_regular"
|
||||||
|
android:textColor="@color/color_3bb9f1"
|
||||||
|
android:textSize="14sp"
|
||||||
|
tools:text="#커버곡 #라이브 #연애 #썸 #채팅 #라방" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 세계관 섹션 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/ll_worldview_section"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="24dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<!-- 섹션 제목 -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_worldview_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="@font/pretendard_bold"
|
||||||
|
android:text="[세계관 및 작품 소개]"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<!-- 세계관 내용 -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_worldview_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:fontFamily="@font/pretendard_regular"
|
||||||
|
android:lineSpacingExtra="4dp"
|
||||||
|
android:textColor="@color/color_b0bec5"
|
||||||
|
android:textSize="16sp"
|
||||||
|
tools:text="특별한 꽃을 길러낼 수 있는 능력을 가진 리엘라.\n\n그녀는 호손 공작의 상속자가 되고 말아버리는데..." />
|
||||||
|
|
||||||
|
<!-- 더보기 버튼 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/ll_worldview_expand"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
tools:ignore="UseCompoundDrawables">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_worldview_expand"
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_chevron_down" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_worldview_expand"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="@font/pretendard_regular"
|
||||||
|
android:text="더보기"
|
||||||
|
android:textColor="@color/color_607d8b"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 원작 섹션 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/ll_original_section"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="36dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<!-- 섹션 제목 -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_original_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="@font/pretendard_bold"
|
||||||
|
android:text="[원작]"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<!-- 원작 내용 -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_original_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:fontFamily="@font/pretendard_regular"
|
||||||
|
android:maxLines="3"
|
||||||
|
android:textColor="@color/color_b0bec5"
|
||||||
|
android:textSize="16sp"
|
||||||
|
tools:text="네이버 시리즈 독 안에 든 선생님" />
|
||||||
|
|
||||||
|
<!-- 원작 보러가기 버튼 -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_original_link"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="54dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:background="@drawable/bg_round_corner_16_stroke_3bb9f1"
|
||||||
|
android:fontFamily="@font/pretendard_bold"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="원작 보러가기"
|
||||||
|
android:textColor="@color/color_3bb9f1"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 성격 섹션 (세계관과 동일 UI) -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/ll_personality_section"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="24dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<!-- 섹션 제목 -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_personality_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="@font/pretendard_bold"
|
||||||
|
android:text="[성격 및 특징]"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<!-- 성격 내용 -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_personality_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:fontFamily="@font/pretendard_regular"
|
||||||
|
android:lineSpacingExtra="4dp"
|
||||||
|
android:textColor="@color/color_b0bec5"
|
||||||
|
android:textSize="16sp"
|
||||||
|
tools:text="밝고 쾌활하지만 때로는 고집이 센 면모도 있습니다.\n\n친구를 소중히 여기며, 어려움 앞에서도 물러서지 않습니다." />
|
||||||
|
|
||||||
|
<!-- 더보기 버튼 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/ll_personality_expand"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
tools:ignore="UseCompoundDrawables">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_personality_expand"
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_chevron_down" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_personality_expand"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="@font/pretendard_regular"
|
||||||
|
android:text="더보기"
|
||||||
|
android:textColor="@color/color_607d8b"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 캐릭터톡 대화 가이드 섹션 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/ll_chat_guide_section"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="24dp"
|
||||||
|
android:layout_marginBottom="24dp"
|
||||||
|
android:background="@drawable/bg_round_corner_16_stroke_37474f"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingHorizontal="16dp"
|
||||||
|
android:paddingVertical="12dp">
|
||||||
|
|
||||||
|
<!-- 가이드 제목 -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_chat_guide_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="@font/pretendard_bold"
|
||||||
|
android:text="⚠️ 캐릭터톡 대화 가이드"
|
||||||
|
android:textColor="@color/color_b0bec5"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<!-- 가이드 내용 -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_chat_guide_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:fontFamily="@font/pretendard_regular"
|
||||||
|
android:lineSpacingExtra="4dp"
|
||||||
|
android:text="보이스온 AI캐릭터톡은 대화의 자유도가 높아 대화에 참여하는 당신은 누구든 될 수 있습니다.\n세계관 속 캐릭터로 대화를 하거나 새로운 인물로 캐릭터와 당신만의 스토리를 만들어보세요."
|
||||||
|
android:textColor="@color/color_7c7c80"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<!-- 주의사항 -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_chat_guide_notice"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:fontFamily="@font/pretendard_regular"
|
||||||
|
android:lineSpacingExtra="4dp"
|
||||||
|
android:text="※ AI캐릭터톡은 오픈베타 서비스 중이며, 캐릭터의 대화가 어색하거나 불완전할 수 있습니다. 대화 초반에 캐릭터 붕괴가 느껴진다면 대화를 리셋하고 다시 시도해보세요."
|
||||||
|
android:textColor="@color/color_7c7c80"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- 댓글 섹션 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/ll_comments_section"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="24dp"
|
||||||
|
android:layout_marginBottom="24dp"
|
||||||
|
android:background="@drawable/bg_round_corner_10_263238"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingHorizontal="16dp"
|
||||||
|
android:paddingVertical="12dp">
|
||||||
|
|
||||||
|
<!-- 헤더: 댓글 (댓글 수) -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_comments_label"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="@font/pretendard_regular"
|
||||||
|
android:text="댓글"
|
||||||
|
android:textColor="@color/color_b0bec5"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_comments_count"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:fontFamily="@font/pretendard_bold"
|
||||||
|
android:textColor="@color/color_b0bec5"
|
||||||
|
android:textSize="16sp"
|
||||||
|
tools:text="0" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 내용 컨테이너 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/ll_comments_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<!-- 댓글 있을 때 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/ll_latest_comment"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:ignore="UseCompoundDrawables">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_comment_profile"
|
||||||
|
android:layout_width="36dp"
|
||||||
|
android:layout_height="36dp"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_placeholder_profile" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_latest_comment"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:fontFamily="@font/pretendard_regular"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="16sp"
|
||||||
|
tools:text="가장 최근 댓글 내용이 여기에 표시됩니다." />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 댓글 없을 때 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/ll_no_comment"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_my_profile"
|
||||||
|
android:layout_width="36dp"
|
||||||
|
android:layout_height="36dp"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_placeholder_profile" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/ll_comment_input_box"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:background="@drawable/bg_round_corner_5_stroke_white"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingHorizontal="12dp"
|
||||||
|
android:paddingVertical="8dp">
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/et_comment_input"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="4dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:fontFamily="@font/pretendard_regular"
|
||||||
|
android:hint="댓글을 입력해보세요"
|
||||||
|
android:imeOptions="actionSend"
|
||||||
|
android:importantForAutofill="no"
|
||||||
|
android:inputType="textCapSentences|textMultiLine"
|
||||||
|
android:maxLines="3"
|
||||||
|
android:padding="0dp"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textColorHint="@color/color_7c7c80"
|
||||||
|
android:textSize="14sp"
|
||||||
|
tools:ignore="NestedWeights" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_send_comment"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_message_send" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 장르의 다른 캐릭터 섹션 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/ll_other_characters_section"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="24dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<!-- 섹션 제목 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingHorizontal="24dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_other_characters_title"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:fontFamily="@font/pretendard_bold"
|
||||||
|
android:text="장르의 다른 캐릭터"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="26sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 캐릭터 리스트 -->
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/rv_other_characters"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:paddingHorizontal="24dp" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
||||||
|
<!-- 하단 고정 대화하기 버튼 -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/btn_chat"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="54dp"
|
||||||
|
android:layout_marginHorizontal="24dp"
|
||||||
|
android:layout_marginBottom="10dp"
|
||||||
|
android:background="@drawable/bg_round_corner_16_solid_3bb9f1"
|
||||||
|
android:fontFamily="@font/pretendard_bold"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="대화하기"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="16sp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import io.mockk.coVerify
|
|||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import io.reactivex.rxjava3.core.Single
|
import io.reactivex.rxjava3.core.Single
|
||||||
|
import kr.co.vividnext.sodalive.chat.character.detail.detail.CharacterType
|
||||||
import kr.co.vividnext.sodalive.chat.talk.TalkApi
|
import kr.co.vividnext.sodalive.chat.talk.TalkApi
|
||||||
import kr.co.vividnext.sodalive.chat.talk.room.db.ChatMessageDao
|
import kr.co.vividnext.sodalive.chat.talk.room.db.ChatMessageDao
|
||||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||||
@@ -23,7 +24,7 @@ class ChatRepositoryTest {
|
|||||||
ServerChatMessage(1, "a1", "", mine = false, createdAt = 1000L),
|
ServerChatMessage(1, "a1", "", mine = false, createdAt = 1000L),
|
||||||
ServerChatMessage(2, "u1", "", mine = true, createdAt = 2000L)
|
ServerChatMessage(2, "u1", "", mine = true, createdAt = 2000L)
|
||||||
)
|
)
|
||||||
val character = CharacterInfo(10, "name", "", kr.co.vividnext.sodalive.chat.character.detail.CharacterType.CLONE)
|
val character = CharacterInfo(10, "name", "", CharacterType.CLONE)
|
||||||
val resp = ChatRoomEnterResponse(99, character, serverMessages, hasMoreMessages = false)
|
val resp = ChatRoomEnterResponse(99, character, serverMessages, hasMoreMessages = false)
|
||||||
|
|
||||||
every { api.enterChatRoom(any(), any()) } returns Single.just(ApiResponse(true, resp, null))
|
every { api.enterChatRoom(any(), any()) } returns Single.just(ApiResponse(true, resp, null))
|
||||||
|
|||||||
Reference in New Issue
Block a user