diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/audio/CreatorChannelAudioFragment.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/audio/CreatorChannelAudioFragment.kt index 11baf9dc..4d9a7cbc 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/audio/CreatorChannelAudioFragment.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/audio/CreatorChannelAudioFragment.kt @@ -5,14 +5,19 @@ import android.text.SpannableString import android.text.Spanned import android.text.style.ForegroundColorSpan import android.view.View +import android.widget.Toast import androidx.core.content.ContextCompat import androidx.core.view.isVisible +import androidx.core.view.updatePadding 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.dpToPx 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.ui.CreatorChannelAudioContentAdapter +import kr.co.vividnext.sodalive.v2.creator.channel.ui.CreatorChannelSortPopup import kr.co.vividnext.sodalive.v2.creator.channel.model.toLabelResId import org.koin.androidx.viewmodel.ext.android.viewModel @@ -21,7 +26,16 @@ class CreatorChannelAudioFragment : BaseFragment + host.onCreatorChannelAudioContentClicked(item.audioContentId) + } + private var sortPopup: CreatorChannelSortPopup? = null + private var currentContentState: CreatorChannelAudioUiState.Content? = null + private var lastContentLayoutKey: CreatorChannelAudioContentLayoutKey? = null + private var emptyMinHeight: Int = 0 private val creatorId: Long by lazy { arguments?.getLong(ARG_CREATOR_ID) ?: 0L } + private val host: Host + get() = requireActivity() as Host override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -29,22 +43,32 @@ class CreatorChannelAudioFragment : BaseFragment 0L) { - viewModel.loadAudio(creatorId, isOwner = false) - } } override fun onDestroyView() { + sortPopup?.dismiss() + sortPopup = null + currentContentState = null + lastContentLayoutKey = null binding.rvCreatorChannelAudioContents.adapter = null super.onDestroyView() } private fun setupAudioList() = with(binding.rvCreatorChannelAudioContents) { layoutManager = LinearLayoutManager(requireContext()) + adapter = audioContentAdapter } private fun setupClickListeners() = with(binding) { ivCreatorChannelAudioSort.setImageResource(R.drawable.ic_new_sort) + layoutCreatorChannelAudioSortButton.setOnClickListener { + currentContentState?.let { state -> showSortPopup(state) } + } + viewCreatorChannelAudioThemeTabs.root.setOnTabSelectedListener { index -> + currentContentState?.themes?.getOrNull(index)?.let { theme -> + viewModel.changeTheme(theme.themeId) + } + } btnCreatorChannelAudioRetry.setOnClickListener { viewModel.retryAudio() } @@ -61,7 +85,38 @@ class CreatorChannelAudioFragment : BaseFragment 0L) { + viewModel.loadAudio(creatorId, isOwner = host.isCreatorChannelOwner()) + } + } + + fun onCreatorChannelAudioScrolledToBottom() { + viewModel.loadMore() + } + + fun onCreatorChannelAudioViewportHeightChanged(minHeight: Int) { + emptyMinHeight = minHeight.coerceAtLeast(0) + applyEmptyMinHeight() + } + + fun onCreatorChannelAudioOwnerCtaVisibilityChanged(isVisible: Boolean) = with(binding) { + val bottomPadding = if (isVisible) { + OWNER_CTA_LIST_BOTTOM_PADDING_DP.dpToPx().toInt() + } else { + DEFAULT_LIST_BOTTOM_PADDING_DP.dpToPx().toInt() + } + rvCreatorChannelAudioContents.updatePadding(bottom = bottomPadding) + layoutCreatorChannelAudioEmpty.updatePadding(bottom = bottomPadding) + } + + private fun applyEmptyMinHeight() = with(binding) { + layoutCreatorChannelAudioEmpty.minimumHeight = emptyMinHeight + } + private fun bindLoading() = with(binding) { + currentContentState = null + lastContentLayoutKey = null viewCreatorChannelAudioThemeTabs.root.isVisible = false layoutCreatorChannelAudioSortBar.isVisible = false layoutCreatorChannelAudioRateCard.isVisible = false @@ -72,16 +127,22 @@ class CreatorChannelAudioFragment : BaseFragment viewModel.changeSort(sort) } + ).also { it.show() } } private fun bindRate(rate: CreatorChannelAudioRateUiModel?) = with(binding) { @@ -142,8 +228,16 @@ class CreatorChannelAudioFragment : BaseFragment +) + +private fun CreatorChannelAudioUiState.Content.toContentLayoutKey(): CreatorChannelAudioContentLayoutKey { + return CreatorChannelAudioContentLayoutKey( + audioContentCount = audioContentCount, + selectedThemeId = selectedThemeId, + audioContentIds = audioContents.map { it.audioContentId } + ) +} diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/audio/CreatorChannelAudioFragmentLayoutTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/audio/CreatorChannelAudioFragmentLayoutTest.kt index 89c7f930..abbb65ce 100644 --- a/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/audio/CreatorChannelAudioFragmentLayoutTest.kt +++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/audio/CreatorChannelAudioFragmentLayoutTest.kt @@ -84,7 +84,7 @@ class CreatorChannelAudioFragmentLayoutTest { val item = inflateView(R.layout.item_creator_channel_audio_content) val itemLayout = projectFile("app/src/main/res/layout/item_creator_channel_audio_content.xml").readText() val adapter = projectFile( - "app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/live/ui/CreatorChannelLiveReplayAdapter.kt" + "app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/ui/CreatorChannelAudioContentAdapter.kt" ).readText() val thumbnail = requireNotNull(item.findViewById(R.id.layout_creator_channel_audio_content_thumbnail)) @@ -120,7 +120,14 @@ class CreatorChannelAudioFragmentLayoutTest { assertTrue(fragment.contains("private val viewModel: CreatorChannelAudioViewModel by viewModel()")) assertTrue(fragment.contains("fun newInstance(creatorId: Long): CreatorChannelAudioFragment")) assertTrue(fragment.contains("arguments = Bundle().apply { putLong(ARG_CREATOR_ID, creatorId) }")) + assertTrue(fragment.contains("fun onCreatorChannelAudioTabSelected()")) + assertTrue(fragment.contains("viewModel.loadAudio(creatorId, isOwner = host.isCreatorChannelOwner())")) + assertTrue(fragment.contains("fun onCreatorChannelAudioViewportHeightChanged(minHeight: Int)")) + assertTrue(fragment.contains("layoutCreatorChannelAudioEmpty.minimumHeight = emptyMinHeight")) + assertTrue(fragment.contains("notifyContentChangedIfLayoutChanged(state)")) + assertTrue(fragment.contains("if (contentLayoutKey == lastContentLayoutKey) return")) assertTrue(fragment.contains("viewModel.audioStateLiveData.observe(viewLifecycleOwner)")) + assertTrue(!fragment.contains("observeViewModel()\n if (creatorId > 0L)")) assertTrue(fragment.contains("CreatorChannelAudioUiState.Loading -> bindLoading()")) assertTrue(fragment.contains("CreatorChannelAudioUiState.Empty -> bindEmpty()")) assertTrue(fragment.contains("is CreatorChannelAudioUiState.Error -> bindError(state)"))