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 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( 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.main.banner.AudioContentMainBannerAdapter
import kr.co.vividnext.sodalive.audio_content.series.detail.SeriesDetailActivity import kr.co.vividnext.sodalive.audio_content.series.detail.SeriesDetailActivity
import kr.co.vividnext.sodalive.base.BaseFragment 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.curation.CurationSectionAdapter
import kr.co.vividnext.sodalive.chat.character.recent.RecentCharacter import kr.co.vividnext.sodalive.chat.character.recent.RecentCharacter
import kr.co.vividnext.sodalive.chat.character.recent.RecentCharacterAdapter import kr.co.vividnext.sodalive.chat.character.recent.RecentCharacterAdapter
@@ -48,7 +47,6 @@ class CharacterTabFragment : BaseFragment<FragmentCharacterTabBinding>(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setupView() setupView()
loadData()
viewModel.fetchData() viewModel.fetchData()
} }
@@ -135,6 +133,15 @@ class CharacterTabFragment : BaseFragment<FragmentCharacterTabBinding>(
) )
.setIndicatorSliderWidth(10f.dpToPx().toInt(), 10f.dpToPx().toInt()) .setIndicatorSliderWidth(10f.dpToPx().toInt(), 10f.dpToPx().toInt())
.setIndicatorHeight(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() { private fun setupRecentCharactersRecyclerView() {
@@ -180,6 +187,17 @@ class CharacterTabFragment : BaseFragment<FragmentCharacterTabBinding>(
}) })
recyclerView.adapter = recentCharacterAdapter 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() { private fun setupPopularCharactersRecyclerView() {
@@ -230,6 +248,16 @@ class CharacterTabFragment : BaseFragment<FragmentCharacterTabBinding>(
binding.tvPopularCharacterAll.setOnClickListener { 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() { private fun setupNewCharactersRecyclerView() {
@@ -280,6 +308,16 @@ class CharacterTabFragment : BaseFragment<FragmentCharacterTabBinding>(
binding.tvNewCharacterAll.setOnClickListener { 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() { private fun setupCurationSectionsRecyclerView() {
@@ -326,79 +364,18 @@ class CharacterTabFragment : BaseFragment<FragmentCharacterTabBinding>(
}) })
recyclerView.adapter = curationSectionAdapter 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) { private fun onRecentCharacterClick(character: RecentCharacter) {
// TODO: 최근 대화한 캐릭터 클릭 처리 // TODO: 최근 대화한 캐릭터 클릭 처리

View File

@@ -5,7 +5,10 @@ import androidx.lifecycle.MutableLiveData
import com.orhanobut.logger.Logger import com.orhanobut.logger.Logger
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.schedulers.Schedulers 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.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 import kr.co.vividnext.sodalive.common.SharedPreferenceManager
class CharacterTabViewModel( class CharacterTabViewModel(
@@ -19,6 +22,30 @@ class CharacterTabViewModel(
val toastLiveData: LiveData<String?> val toastLiveData: LiveData<String?>
get() = _toastLiveData 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() { fun fetchData() {
_isLoading.value = true _isLoading.value = true
@@ -31,7 +58,11 @@ class CharacterTabViewModel(
_isLoading.value = false _isLoading.value = false
val data = it.data val data = it.data
if (it.success && data != null) { 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 <LinearLayout
android:id="@+id/ll_latest_characters"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="48dp" android:layout_marginBottom="48dp"
@@ -83,6 +84,7 @@
<!-- 인기 캐릭터 섹션 --> <!-- 인기 캐릭터 섹션 -->
<LinearLayout <LinearLayout
android:id="@+id/ll_popular_characters"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="48dp" android:layout_marginBottom="48dp"
@@ -127,6 +129,7 @@
<!-- 신규 캐릭터 섹션 --> <!-- 신규 캐릭터 섹션 -->
<LinearLayout <LinearLayout
android:id="@+id/ll_new_characters"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="48dp" android:layout_marginBottom="48dp"