feat(content): 랭킹 탭 화면 연결을 추가한다

This commit is contained in:
2026-06-24 14:45:45 +09:00
parent cf89052806
commit 2818f8d4a4
2 changed files with 142 additions and 9 deletions

View File

@@ -14,6 +14,8 @@ import kr.co.vividnext.sodalive.common.LoadingDialog
import kr.co.vividnext.sodalive.common.ToastMessage
import kr.co.vividnext.sodalive.databinding.FragmentV2MainContentBinding
import kr.co.vividnext.sodalive.databinding.ViewSectionTitleBinding
import kr.co.vividnext.sodalive.v2.main.content.data.AudioRankingType
import kr.co.vividnext.sodalive.v2.main.content.model.AudioRankingsUiState
import kr.co.vividnext.sodalive.v2.main.content.model.AudioRecommendationsUiState
import kr.co.vividnext.sodalive.v2.main.content.model.ContentAudioCardSection
import kr.co.vividnext.sodalive.v2.main.content.model.ContentAudioCardUiModel
@@ -34,12 +36,15 @@ import kr.co.vividnext.sodalive.v2.main.content.ui.ContentOriginalSeriesAdapter
import kr.co.vividnext.sodalive.v2.main.content.ui.addContentGridItemSpacing
import kr.co.vividnext.sodalive.v2.main.content.ui.addContentHorizontalItemSpacing
import kr.co.vividnext.sodalive.v2.widget.AudioContentCardSize
import kr.co.vividnext.sodalive.v2.widget.contentranking.ContentRankingAdapter
import kr.co.vividnext.sodalive.v2.widget.contentranking.ContentRankingItem
import org.koin.androidx.viewmodel.ext.android.viewModel
class ContentMainFragment : BaseFragment<FragmentV2MainContentBinding>(
FragmentV2MainContentBinding::inflate
) {
private val contentMainViewModel: ContentMainViewModel by viewModel()
private val contentRankingViewModel: ContentRankingViewModel by viewModel()
private val loadingDialog: LoadingDialog by lazy { LoadingDialog(requireActivity(), layoutInflater) }
private val originalSeriesAdapter = ContentOriginalSeriesAdapter { openSeriesDetail(it) }
private val latestAudioAdapter = ContentAudioCardAdapter(AudioContentCardSize.Medium) { openAudioContentDetail(it) }
@@ -48,20 +53,77 @@ class ContentMainFragment : BaseFragment<FragmentV2MainContentBinding>(
private val pointAudioAdapter = ContentAudioCardAdapter(AudioContentCardSize.Medium) { openAudioContentDetail(it) }
private val commentedAudioAdapter = ContentCommentedAudioAdapter { openAudioContentDetail(it) }
private val recommendedAudioAdapter = ContentAudioCardAdapter(AudioContentCardSize.Large) { openAudioContentDetail(it) }
private val contentRankingAdapter = ContentRankingAdapter { openRankingAudioContentDetail(it) }
private var bannerBinder: ContentBannerBinder? = null
private var isRecommendationLoading = false
private var isRankingLoading = false
private var hasSelectedRankingTab = false
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.textTabBarContent.root.setMenus(
listOf(getString(R.string.screen_content_tab_recommendation)),
selectedIndex = 0
)
setUpTextTabs()
setUpRankingTypeTabs()
showContentTab(CONTENT_TAB_RECOMMENDATION)
setUpSectionTitles()
setUpAdapters()
bindObservers()
contentMainViewModel.loadRecommendations()
}
private fun setUpTextTabs() {
binding.textTabBarContent.root.setMenus(
listOf(
getString(R.string.screen_content_tab_recommendation),
getString(R.string.screen_content_tab_ranking),
getString(R.string.screen_content_tab_all)
),
selectedIndex = 0
)
binding.textTabBarContent.root.setOnTabSelectedListener { index ->
showContentTab(index)
}
}
private fun setUpRankingTypeTabs() {
binding.viewContentRankingTypeTabs.root.setMenus(
AudioRankingType.entries.map { type -> getString(type.labelResId()) },
selectedIndex = AudioRankingType.WEEKLY_POPULAR.ordinal
)
binding.viewContentRankingTypeTabs.root.setOnTabSelectedListener { index ->
contentRankingViewModel.loadRankings(AudioRankingType.entries[index])
}
}
private fun showContentTab(index: Int) {
when (index) {
CONTENT_TAB_RECOMMENDATION -> showRecommendationContent()
CONTENT_TAB_RANKING -> showRankingContent()
CONTENT_TAB_ALL -> hideContentSurfaces()
}
}
private fun showRecommendationContent() {
binding.nsvContentRecommendationContent.visibility = View.VISIBLE
binding.viewContentRankingTypeTabs.root.visibility = View.GONE
binding.rvContentRankings.visibility = View.GONE
}
private fun showRankingContent() {
binding.nsvContentRecommendationContent.visibility = View.GONE
binding.viewContentRankingTypeTabs.root.visibility = View.VISIBLE
binding.rvContentRankings.visibility = View.VISIBLE
if (!hasSelectedRankingTab) {
hasSelectedRankingTab = true
contentRankingViewModel.loadRankings(AudioRankingType.WEEKLY_POPULAR)
}
}
private fun hideContentSurfaces() {
binding.nsvContentRecommendationContent.visibility = View.GONE
binding.viewContentRankingTypeTabs.root.visibility = View.GONE
binding.rvContentRankings.visibility = View.GONE
}
private fun setUpAdapters() {
bannerBinder = ContentBannerBinder(binding.rvContentBanners).apply {
setOnBannerClick { onBannerClick(it) }
@@ -101,6 +163,10 @@ class ContentMainFragment : BaseFragment<FragmentV2MainContentBinding>(
adapter = recommendedAudioAdapter
addContentGridItemSpacing()
}
binding.rvContentRankings.apply {
layoutManager = ContentRankingAdapter.createGridLayoutManager(requireContext())
adapter = contentRankingAdapter
}
}
private fun bindObservers() {
@@ -114,15 +180,28 @@ class ContentMainFragment : BaseFragment<FragmentV2MainContentBinding>(
}
}
contentMainViewModel.isLoading.observe(viewLifecycleOwner) { isLoading ->
if (isLoading) {
loadingDialog.show(screenWidth)
} else {
loadingDialog.dismiss()
}
isRecommendationLoading = isLoading
updateLoadingDialog()
}
contentMainViewModel.toastLiveData.observe(viewLifecycleOwner) { toastMessage ->
toastMessage?.let(::showToast)
}
contentRankingViewModel.rankingStateLiveData.observe(viewLifecycleOwner) { state ->
when (state) {
is AudioRankingsUiState.Content -> contentRankingAdapter.submitItems(state.items)
is AudioRankingsUiState.Empty,
is AudioRankingsUiState.Error -> contentRankingAdapter.submitItems(emptyList())
AudioRankingsUiState.Loading -> Unit
}
}
contentRankingViewModel.isLoading.observe(viewLifecycleOwner) { isLoading ->
isRankingLoading = isLoading
updateLoadingDialog()
}
contentRankingViewModel.toastLiveData.observe(viewLifecycleOwner) { toastMessage ->
toastMessage?.let(::showToast)
}
}
private fun bindContent(content: AudioRecommendationsUiState.Content) {
@@ -213,6 +292,11 @@ class ContentMainFragment : BaseFragment<FragmentV2MainContentBinding>(
)
}
private fun openRankingAudioContentDetail(item: ContentRankingItem) {
val audioContentId = item.contentId.toLongOrNull() ?: return
openAudioContentDetail(audioContentId)
}
private fun openSeriesDetail(item: ContentOriginalSeriesUiModel) {
val seriesId = item.seriesId.takeIf { it > 0L } ?: return
startActivity(
@@ -227,6 +311,14 @@ class ContentMainFragment : BaseFragment<FragmentV2MainContentBinding>(
?: toastMessage.resId?.let { resId -> showToast(getString(resId)) }
}
private fun updateLoadingDialog() {
if (isRecommendationLoading || isRankingLoading) {
loadingDialog.show(screenWidth)
} else {
loadingDialog.dismiss()
}
}
private fun emptyContent(): AudioRecommendationsUiState.Content {
return AudioRecommendationsUiState.Content(
banners = ContentBannerSection(emptyList()),
@@ -241,4 +333,19 @@ class ContentMainFragment : BaseFragment<FragmentV2MainContentBinding>(
}
private fun List<*>.toSectionVisibility(): Int = if (isEmpty()) View.GONE else View.VISIBLE
private fun AudioRankingType.labelResId(): Int = when (this) {
AudioRankingType.WEEKLY_POPULAR -> R.string.screen_content_ranking_type_weekly_popular
AudioRankingType.RISING -> R.string.screen_content_ranking_type_rising
AudioRankingType.REVENUE -> R.string.screen_content_ranking_type_revenue
AudioRankingType.SALES_COUNT -> R.string.screen_content_ranking_type_sales_count
AudioRankingType.COMMENT_COUNT -> R.string.screen_content_ranking_type_comment_count
AudioRankingType.LIKE_COUNT -> R.string.screen_content_ranking_type_like_count
}
companion object {
private const val CONTENT_TAB_RECOMMENDATION = 0
private const val CONTENT_TAB_RANKING = 1
private const val CONTENT_TAB_ALL = 2
}
}