feat(ui): 캐릭터 탭
- 섹션별로 데이터가 있으면 보여주고 없으면 UI를 제거하도록 로직 추가
This commit is contained in:
		@@ -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>
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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: 최근 대화한 캐릭터 클릭 처리
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    },
 | 
					                    },
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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"
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user