feat(creator): 오디오 탭 fragment 골격을 추가한다

This commit is contained in:
2026-06-19 19:13:00 +09:00
parent 0b2faf2c6e
commit e12f00b5b4
3 changed files with 347 additions and 4 deletions

View File

@@ -0,0 +1,154 @@
package kr.co.vividnext.sodalive.v2.creator.channel.audio
import android.os.Bundle
import android.text.SpannableString
import android.text.Spanned
import android.text.style.ForegroundColorSpan
import android.view.View
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.recyclerview.widget.LinearLayoutManager
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.base.BaseFragment
import kr.co.vividnext.sodalive.databinding.FragmentCreatorChannelAudioBinding
import kr.co.vividnext.sodalive.extensions.moneyFormat
import kr.co.vividnext.sodalive.v2.creator.channel.audio.model.CreatorChannelAudioRateUiModel
import kr.co.vividnext.sodalive.v2.creator.channel.model.toLabelResId
import org.koin.androidx.viewmodel.ext.android.viewModel
class CreatorChannelAudioFragment : BaseFragment<FragmentCreatorChannelAudioBinding>(
FragmentCreatorChannelAudioBinding::inflate
) {
private val viewModel: CreatorChannelAudioViewModel by viewModel()
private val creatorId: Long by lazy { arguments?.getLong(ARG_CREATOR_ID) ?: 0L }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
bindLoading()
setupAudioList()
setupClickListeners()
observeViewModel()
if (creatorId > 0L) {
viewModel.loadAudio(creatorId, isOwner = false)
}
}
override fun onDestroyView() {
binding.rvCreatorChannelAudioContents.adapter = null
super.onDestroyView()
}
private fun setupAudioList() = with(binding.rvCreatorChannelAudioContents) {
layoutManager = LinearLayoutManager(requireContext())
}
private fun setupClickListeners() = with(binding) {
ivCreatorChannelAudioSort.setImageResource(R.drawable.ic_new_sort)
btnCreatorChannelAudioRetry.setOnClickListener {
viewModel.retryAudio()
}
}
private fun observeViewModel() {
viewModel.audioStateLiveData.observe(viewLifecycleOwner) { state ->
when (state) {
CreatorChannelAudioUiState.Loading -> bindLoading()
CreatorChannelAudioUiState.Empty -> bindEmpty()
is CreatorChannelAudioUiState.Error -> bindError(state)
is CreatorChannelAudioUiState.Content -> bindContent(state)
}
}
}
private fun bindLoading() = with(binding) {
viewCreatorChannelAudioThemeTabs.root.isVisible = false
layoutCreatorChannelAudioSortBar.isVisible = false
layoutCreatorChannelAudioRateCard.isVisible = false
rvCreatorChannelAudioContents.isVisible = false
layoutCreatorChannelAudioEmpty.isVisible = false
tvCreatorChannelAudioErrorMessage.isVisible = false
btnCreatorChannelAudioRetry.isVisible = false
}
private fun bindEmpty() = with(binding) {
viewCreatorChannelAudioThemeTabs.root.isVisible = false
layoutCreatorChannelAudioSortBar.isVisible = false
layoutCreatorChannelAudioRateCard.isVisible = false
rvCreatorChannelAudioContents.isVisible = false
layoutCreatorChannelAudioEmpty.isVisible = true
tvCreatorChannelAudioErrorMessage.isVisible = false
btnCreatorChannelAudioRetry.isVisible = false
}
private fun bindError(state: CreatorChannelAudioUiState.Error) = with(binding) {
viewCreatorChannelAudioThemeTabs.root.isVisible = false
layoutCreatorChannelAudioSortBar.isVisible = false
layoutCreatorChannelAudioRateCard.isVisible = false
rvCreatorChannelAudioContents.isVisible = false
layoutCreatorChannelAudioEmpty.isVisible = false
tvCreatorChannelAudioErrorMessage.isVisible = true
tvCreatorChannelAudioErrorMessage.text = state.message ?: getString(R.string.creator_channel_audio_error_message)
btnCreatorChannelAudioRetry.isVisible = true
}
private fun bindContent(state: CreatorChannelAudioUiState.Content) = with(binding) {
viewCreatorChannelAudioThemeTabs.root.isVisible = true
viewCreatorChannelAudioThemeTabs.root.setMenus(
menus = state.themes.map { it.title },
selectedIndex = state.themes.indexOfFirst { it.isSelected }.coerceAtLeast(0)
)
layoutCreatorChannelAudioSortBar.isVisible = true
tvCreatorChannelAudioTotalCount.text = state.audioContentCount.moneyFormat()
tvCreatorChannelAudioSortLabel.setText(state.selectedSort.toLabelResId())
bindRate(state.rate)
rvCreatorChannelAudioContents.isVisible = true
layoutCreatorChannelAudioEmpty.isVisible = false
tvCreatorChannelAudioErrorMessage.isVisible = false
btnCreatorChannelAudioRetry.isVisible = false
}
private fun bindRate(rate: CreatorChannelAudioRateUiModel?) = with(binding) {
layoutCreatorChannelAudioRateCard.isVisible = rate != null
if (rate == null) return@with
val ratePercentText = rate.ratePercent.toInt().toString()
val rateMessage = getString(
R.string.creator_channel_audio_owned_rate_message,
ratePercentText
)
tvCreatorChannelAudioRateMessage.text = rateMessage.highlightRatePercent(ratePercentText)
tvCreatorChannelAudioRateCount.text = getString(
R.string.creator_channel_audio_owned_rate_count,
rate.purchasedCount.moneyFormat(),
rate.paidCount.moneyFormat()
)
viewCreatorChannelAudioRateFill.pivotX = 0f
viewCreatorChannelAudioRateFill.scaleX = (rate.ratePercent / 100.0).toFloat().coerceIn(0f, 1f)
}
private fun String.highlightRatePercent(ratePercentText: String): SpannableString {
val spannable = SpannableString(this)
val target = "$ratePercentText%"
val start = indexOf(target)
if (start < 0) return spannable
spannable.setSpan(
ForegroundColorSpan(ContextCompat.getColor(requireContext(), R.color.soda_400)),
start,
start + target.length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
return spannable
}
companion object {
private const val ARG_CREATOR_ID: String = "arg_creator_id"
fun newInstance(creatorId: Long): CreatorChannelAudioFragment {
return CreatorChannelAudioFragment().apply {
arguments = Bundle().apply { putLong(ARG_CREATOR_ID, creatorId) }
}
}
}
}