feat(ui): 캐릭터 탭

- 섹션별로 데이터가 있으면 보여주고 없으면 UI를 제거하도록 로직 추가
This commit is contained in:
2025-08-04 23:38:51 +09:00
parent f0eda41c7c
commit 93fc837b7a
4 changed files with 94 additions and 75 deletions

View File

@@ -1,5 +1,13 @@
package kr.co.vividnext.sodalive.chat.character
import kr.co.vividnext.sodalive.audio_content.main.GetAudioContentBannerResponse
import kr.co.vividnext.sodalive.chat.character.curation.CurationSection
import kr.co.vividnext.sodalive.chat.character.recent.RecentCharacter
data class CharacterHomeResponse(
val id: Long
val banners: List<GetAudioContentBannerResponse>,
val recentCharacters: List<RecentCharacter>,
val popularCharacters: List<Character>,
val newCharacters: List<Character>,
val curationSections: List<CurationSection>
)

View File

@@ -19,7 +19,6 @@ import kr.co.vividnext.sodalive.audio_content.main.AudioContentBannerType
import kr.co.vividnext.sodalive.audio_content.main.banner.AudioContentMainBannerAdapter
import kr.co.vividnext.sodalive.audio_content.series.detail.SeriesDetailActivity
import kr.co.vividnext.sodalive.base.BaseFragment
import kr.co.vividnext.sodalive.chat.character.curation.CurationSection
import kr.co.vividnext.sodalive.chat.character.curation.CurationSectionAdapter
import kr.co.vividnext.sodalive.chat.character.recent.RecentCharacter
import kr.co.vividnext.sodalive.chat.character.recent.RecentCharacterAdapter
@@ -48,7 +47,6 @@ class CharacterTabFragment : BaseFragment<FragmentCharacterTabBinding>(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupView()
loadData()
viewModel.fetchData()
}
@@ -135,6 +133,15 @@ class CharacterTabFragment : BaseFragment<FragmentCharacterTabBinding>(
)
.setIndicatorSliderWidth(10f.dpToPx().toInt(), 10f.dpToPx().toInt())
.setIndicatorHeight(10f.dpToPx().toInt())
viewModel.bannerListLiveData.observe(viewLifecycleOwner) {
if (it.isNotEmpty()) {
binding.llBanner.visibility = View.VISIBLE
binding.bannerSlider.refreshData(it)
} else {
binding.llBanner.visibility = View.GONE
}
}
}
private fun setupRecentCharactersRecyclerView() {
@@ -180,6 +187,17 @@ class CharacterTabFragment : BaseFragment<FragmentCharacterTabBinding>(
})
recyclerView.adapter = recentCharacterAdapter
// 최근 대화한 캐릭터 LiveData 구독
viewModel.recentCharacters.observe(viewLifecycleOwner) {
if (it.isNotEmpty()) {
binding.llLatestCharacters.visibility = View.VISIBLE
recentCharacterAdapter.updateCharacters(it)
binding.tvLatestCharacterCount.text = it.size.toString()
} else {
binding.llLatestCharacters.visibility = View.GONE
}
}
}
private fun setupPopularCharactersRecyclerView() {
@@ -230,6 +248,16 @@ class CharacterTabFragment : BaseFragment<FragmentCharacterTabBinding>(
binding.tvPopularCharacterAll.setOnClickListener {
}
// 인기 캐릭터 LiveData 구독
viewModel.popularCharacters.observe(viewLifecycleOwner) {
if (it.isNotEmpty()) {
binding.llPopularCharacters.visibility = View.VISIBLE
popularCharacterAdapter.updateCharacters(it)
} else {
binding.llPopularCharacters.visibility = View.GONE
}
}
}
private fun setupNewCharactersRecyclerView() {
@@ -280,6 +308,16 @@ class CharacterTabFragment : BaseFragment<FragmentCharacterTabBinding>(
binding.tvNewCharacterAll.setOnClickListener {
}
// 신규 캐릭터 LiveData 구독
viewModel.newCharacters.observe(viewLifecycleOwner) {
if (it.isNotEmpty()) {
binding.llNewCharacters.visibility = View.VISIBLE
newCharacterAdapter.updateCharacters(it)
} else {
binding.llNewCharacters.visibility = View.GONE
}
}
}
private fun setupCurationSectionsRecyclerView() {
@@ -326,79 +364,18 @@ class CharacterTabFragment : BaseFragment<FragmentCharacterTabBinding>(
})
recyclerView.adapter = curationSectionAdapter
// 큐레이션 섹션 LiveData 구독
viewModel.curationSections.observe(viewLifecycleOwner) {
if (it.isNotEmpty()) {
recyclerView.visibility = View.VISIBLE
curationSectionAdapter.updateSections(it)
} else {
recyclerView.visibility = View.GONE
}
}
}
private fun loadData() {
// TODO: 실제 데이터 로딩 로직 구현
loadRecentCharacters()
loadPopularCharacters()
loadNewCharacters()
loadCurationSections()
}
private fun loadRecentCharacters() {
// TODO: 서버에서 최근 대화한 캐릭터 데이터 로드
val recentCharacters = listOf(
RecentCharacter("1", "Yubin...", ""),
RecentCharacter("2", "Yubin...", ""),
RecentCharacter("3", "Yubin...", ""),
RecentCharacter("4", "Yubin...", ""),
RecentCharacter("5", "Yubin...", "")
)
recentCharacterAdapter.updateCharacters(recentCharacters)
binding.tvLatestCharacterCount.text = recentCharacters.size.toString()
}
private fun loadPopularCharacters() {
// TODO: 서버에서 인기 캐릭터 데이터 로드
val popularCharacters = listOf(
Character("1", "캐릭터 이름", "캐릭터 한줄 소개인데 2줄까", ""),
Character("2", "캐릭터 이름", "캐릭터 한줄 소개인데 2줄까", ""),
Character("3", "캐릭터 이름", "#태그#태그#태그", ""),
Character("4", "캐릭터 이름", "캐릭터 한줄 소개인데 2줄까", ""),
Character("5", "캐릭터 이름", "캐릭터 한줄 소개인데 2줄까", "")
)
popularCharacterAdapter.updateCharacters(popularCharacters)
}
private fun loadNewCharacters() {
// TODO: 서버에서 신규 캐릭터 데이터 로드
val newCharacters = listOf(
Character("1", "캐릭터 이름", "캐릭터 한줄 소개인데 2줄까", ""),
Character("2", "하이퍼나이프", "캐릭터 한줄 소개인데 2줄까", ""),
Character("3", "내일", "#태그#태그", "")
)
newCharacterAdapter.updateCharacters(newCharacters)
}
private fun loadCurationSections() {
// TODO: 서버에서 큐레이션 섹션 데이터 로드
val curationSections = listOf(
CurationSection(
"1",
"큐레이션",
listOf(
Character("1", "캐릭터 이름", "캐릭터 한줄 소개인데 2줄까", ""),
Character("2", "하이퍼나이프", "캐릭터 한줄 소개인데 2줄까", ""),
Character("3", "내일", "#태그#태그", "")
)
),
CurationSection(
"2",
"큐레이션",
listOf(
Character("4", "캐릭터 이름", "캐릭터 한줄 소개인데 2줄까", ""),
Character("5", "하이퍼나이프", "캐릭터 한줄 소개인데 2줄까", ""),
Character("6", "내일", "#태그#태그", "")
)
)
)
curationSectionAdapter.updateSections(curationSections)
}
private fun onRecentCharacterClick(character: RecentCharacter) {
// TODO: 최근 대화한 캐릭터 클릭 처리

View File

@@ -5,7 +5,10 @@ import androidx.lifecycle.MutableLiveData
import com.orhanobut.logger.Logger
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.schedulers.Schedulers
import kr.co.vividnext.sodalive.audio_content.main.GetAudioContentBannerResponse
import kr.co.vividnext.sodalive.base.BaseViewModel
import kr.co.vividnext.sodalive.chat.character.curation.CurationSection
import kr.co.vividnext.sodalive.chat.character.recent.RecentCharacter
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
class CharacterTabViewModel(
@@ -19,6 +22,30 @@ class CharacterTabViewModel(
val toastLiveData: LiveData<String?>
get() = _toastLiveData
private var _bannerListLiveData = MutableLiveData<List<GetAudioContentBannerResponse>>()
val bannerListLiveData: LiveData<List<GetAudioContentBannerResponse>>
get() = _bannerListLiveData
// 최근 대화한 캐릭터 LiveData
private val _recentCharacters = MutableLiveData<List<RecentCharacter>>(emptyList())
val recentCharacters: LiveData<List<RecentCharacter>>
get() = _recentCharacters
// 인기 캐릭터 LiveData
private val _popularCharacters = MutableLiveData<List<Character>>(emptyList())
val popularCharacters: LiveData<List<Character>>
get() = _popularCharacters
// 신규 캐릭터 LiveData
private val _newCharacters = MutableLiveData<List<Character>>(emptyList())
val newCharacters: LiveData<List<Character>>
get() = _newCharacters
// 큐레이션 섹션 LiveData
private val _curationSections = MutableLiveData<List<CurationSection>>(emptyList())
val curationSections: LiveData<List<CurationSection>>
get() = _curationSections
fun fetchData() {
_isLoading.value = true
@@ -31,7 +58,11 @@ class CharacterTabViewModel(
_isLoading.value = false
val data = it.data
if (it.success && data != null) {
_bannerListLiveData.value = data.banners
_recentCharacters.value = data.recentCharacters
_popularCharacters.value = data.popularCharacters
_newCharacters.value = data.newCharacters
_curationSections.value = data.curationSections
}
},
{

View File

@@ -37,6 +37,7 @@
<!-- 최근 대화한 캐릭터 섹션 -->
<LinearLayout
android:id="@+id/ll_latest_characters"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="48dp"
@@ -83,6 +84,7 @@
<!-- 인기 캐릭터 섹션 -->
<LinearLayout
android:id="@+id/ll_popular_characters"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="48dp"
@@ -127,6 +129,7 @@
<!-- 신규 캐릭터 섹션 -->
<LinearLayout
android:id="@+id/ll_new_characters"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="48dp"