콘텐츠 메인 단편 탭
- 태그별 추천 콘텐츠 영역 추가
This commit is contained in:
		@@ -374,4 +374,10 @@ interface AudioContentApi {
 | 
			
		||||
        @Query("creatorId") creatorId: Long,
 | 
			
		||||
        @Header("Authorization") authHeader: String
 | 
			
		||||
    ): Single<ApiResponse<List<GetAudioContentRankingItem>>>
 | 
			
		||||
 | 
			
		||||
    @GET("/v2/audio-content/main/content/recommend-content-by-tag")
 | 
			
		||||
    fun getRecommendedContentByTag(
 | 
			
		||||
        @Query("tag") tag: String,
 | 
			
		||||
        @Header("Authorization") authHeader: String
 | 
			
		||||
    ): Single<ApiResponse<List<GetAudioContentMainItem>>>
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,12 @@
 | 
			
		||||
package kr.co.vividnext.sodalive.audio_content.all.by_theme
 | 
			
		||||
 | 
			
		||||
import android.content.Intent
 | 
			
		||||
import android.graphics.Rect
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import android.view.View
 | 
			
		||||
import android.widget.TextView
 | 
			
		||||
import android.widget.Toast
 | 
			
		||||
import androidx.annotation.OptIn
 | 
			
		||||
import androidx.core.content.ContextCompat
 | 
			
		||||
import androidx.media3.common.util.UnstableApi
 | 
			
		||||
import androidx.recyclerview.widget.GridLayoutManager
 | 
			
		||||
import androidx.recyclerview.widget.LinearLayoutManager
 | 
			
		||||
import androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
@@ -20,7 +20,6 @@ import kr.co.vividnext.sodalive.common.GridSpacingItemDecoration
 | 
			
		||||
import kr.co.vividnext.sodalive.common.LoadingDialog
 | 
			
		||||
import kr.co.vividnext.sodalive.databinding.ActivityAudioContentAllByThemeBinding
 | 
			
		||||
import kr.co.vividnext.sodalive.explorer.profile.UserProfileActivity
 | 
			
		||||
import kr.co.vividnext.sodalive.extensions.dpToPx
 | 
			
		||||
import org.koin.android.ext.android.inject
 | 
			
		||||
 | 
			
		||||
class AudioContentAllByThemeActivity : BaseActivity<ActivityAudioContentAllByThemeBinding>(
 | 
			
		||||
@@ -52,6 +51,7 @@ class AudioContentAllByThemeActivity : BaseActivity<ActivityAudioContentAllByThe
 | 
			
		||||
        viewModel.getContentList(themeId = themeId)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @OptIn(UnstableApi::class)
 | 
			
		||||
    override fun setupView() {
 | 
			
		||||
        loadingDialog = LoadingDialog(this, layoutInflater)
 | 
			
		||||
        binding.toolbar.tvBack.setOnClickListener { finish() }
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,7 @@ import com.zhpan.indicator.enums.IndicatorSlideMode
 | 
			
		||||
import com.zhpan.indicator.enums.IndicatorStyle
 | 
			
		||||
import kr.co.vividnext.sodalive.R
 | 
			
		||||
import kr.co.vividnext.sodalive.audio_content.all.AudioContentNewAllActivity
 | 
			
		||||
import kr.co.vividnext.sodalive.audio_content.all.AudioContentNewAllAdapter
 | 
			
		||||
import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity
 | 
			
		||||
import kr.co.vividnext.sodalive.audio_content.main.AudioContentBannerType
 | 
			
		||||
import kr.co.vividnext.sodalive.audio_content.main.AudioContentMainContentAdapter
 | 
			
		||||
@@ -56,6 +57,8 @@ class AudioContentMainTabContentFragment : BaseFragment<FragmentAudioContentMain
 | 
			
		||||
    private lateinit var contentRankCreatorAdapter: ContentRankCreatorAdapter
 | 
			
		||||
    private lateinit var curationAdapter: AudioContentMainContentCurationAdapter
 | 
			
		||||
    private lateinit var popularContentByCreatorAdapter: PopularContentByCreatorAdapter
 | 
			
		||||
    private lateinit var contentTagAdapter: AudioContentMainTabContentTagAdapter
 | 
			
		||||
    private lateinit var contentByTagAdapter: AudioContentNewAllAdapter
 | 
			
		||||
 | 
			
		||||
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onViewCreated(view, savedInstanceState)
 | 
			
		||||
@@ -76,6 +79,8 @@ class AudioContentMainTabContentFragment : BaseFragment<FragmentAudioContentMain
 | 
			
		||||
        setupEventBanner()
 | 
			
		||||
        setupPopularContentCreator()
 | 
			
		||||
        setupPopularContentByCreator()
 | 
			
		||||
        setupContentTag()
 | 
			
		||||
        setupContentByTag()
 | 
			
		||||
        setupCuration()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -506,6 +511,75 @@ class AudioContentMainTabContentFragment : BaseFragment<FragmentAudioContentMain
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun setupContentTag() {
 | 
			
		||||
        val spanCount = 4
 | 
			
		||||
        val spacing = 6f.dpToPx()
 | 
			
		||||
 | 
			
		||||
        contentTagAdapter = AudioContentMainTabContentTagAdapter {
 | 
			
		||||
            viewModel.getRecommendContentByTag(it)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val recyclerView = binding.rvRecommendContentTag
 | 
			
		||||
        recyclerView.layoutManager = GridLayoutManager(requireContext(), spanCount)
 | 
			
		||||
        recyclerView.addItemDecoration(GridSpacingItemDecoration(spanCount, spacing.toInt(), false))
 | 
			
		||||
        recyclerView.adapter = contentTagAdapter
 | 
			
		||||
 | 
			
		||||
        viewModel.tagListLiveData.observe(viewLifecycleOwner) {
 | 
			
		||||
            contentTagAdapter.addItems(it)
 | 
			
		||||
            if (contentTagAdapter.itemCount <= 0) {
 | 
			
		||||
                binding.llRecommendContentByTag.visibility = View.GONE
 | 
			
		||||
            } else {
 | 
			
		||||
                binding.llRecommendContentByTag.visibility = View.VISIBLE
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun setupContentByTag() {
 | 
			
		||||
        val spanCount = 3
 | 
			
		||||
        val horizontalSpacing = 13.3f.dpToPx().toInt()
 | 
			
		||||
        val verticalSpacing = 26.7f.dpToPx().toInt()
 | 
			
		||||
        val itemWidth = (screenWidth - horizontalSpacing * (spanCount + 1)) / spanCount
 | 
			
		||||
        contentByTagAdapter = AudioContentNewAllAdapter(
 | 
			
		||||
            itemWidth = itemWidth,
 | 
			
		||||
            onClickItem = {
 | 
			
		||||
                startActivity(
 | 
			
		||||
                    Intent(requireContext(), AudioContentDetailActivity::class.java).apply {
 | 
			
		||||
                        putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, it)
 | 
			
		||||
                    }
 | 
			
		||||
                )
 | 
			
		||||
            },
 | 
			
		||||
            onClickCreator = {
 | 
			
		||||
                startActivity(
 | 
			
		||||
                    Intent(requireContext(), UserProfileActivity::class.java).apply {
 | 
			
		||||
                        putExtra(Constants.EXTRA_USER_ID, it)
 | 
			
		||||
                    }
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        val recyclerView = binding.rvRecommendContent
 | 
			
		||||
        recyclerView.layoutManager = GridLayoutManager(requireContext(), spanCount)
 | 
			
		||||
        recyclerView.addItemDecoration(object : RecyclerView.ItemDecoration() {
 | 
			
		||||
            override fun getItemOffsets(
 | 
			
		||||
                outRect: Rect,
 | 
			
		||||
                view: View,
 | 
			
		||||
                parent: RecyclerView,
 | 
			
		||||
                state: RecyclerView.State
 | 
			
		||||
            ) {
 | 
			
		||||
                outRect.left = horizontalSpacing / 2
 | 
			
		||||
                outRect.right = horizontalSpacing / 2
 | 
			
		||||
                outRect.top = verticalSpacing / 2
 | 
			
		||||
                outRect.bottom = verticalSpacing / 2
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
        recyclerView.adapter = contentByTagAdapter
 | 
			
		||||
 | 
			
		||||
        viewModel.tagCurationContentListLiveData.observe(viewLifecycleOwner) {
 | 
			
		||||
            contentByTagAdapter.clear()
 | 
			
		||||
            contentByTagAdapter.addItems(it)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun setupCuration() {
 | 
			
		||||
        curationAdapter = AudioContentMainContentCurationAdapter(
 | 
			
		||||
            onClickItem = {
 | 
			
		||||
 
 | 
			
		||||
@@ -25,4 +25,9 @@ class AudioContentMainTabContentRepository(private val api: AudioContentApi) {
 | 
			
		||||
        creatorId: Long,
 | 
			
		||||
        token: String
 | 
			
		||||
    ) = api.getContentMainContentPopularContentByCreator(creatorId, authHeader = token)
 | 
			
		||||
 | 
			
		||||
    fun getRecommendedContentByTag(
 | 
			
		||||
        tag: String,
 | 
			
		||||
        token: String
 | 
			
		||||
    ) = api.getRecommendedContentByTag(tag = tag, authHeader = token)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,78 @@
 | 
			
		||||
package kr.co.vividnext.sodalive.audio_content.main.v2.content
 | 
			
		||||
 | 
			
		||||
import android.annotation.SuppressLint
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.view.LayoutInflater
 | 
			
		||||
import android.view.ViewGroup
 | 
			
		||||
import android.widget.FrameLayout
 | 
			
		||||
import androidx.core.content.ContextCompat
 | 
			
		||||
import androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
import kr.co.vividnext.sodalive.R
 | 
			
		||||
import kr.co.vividnext.sodalive.databinding.ItemContentMainTabContentTagBinding
 | 
			
		||||
 | 
			
		||||
class AudioContentMainTabContentTagAdapter(
 | 
			
		||||
    private val onClick: (String) -> Unit
 | 
			
		||||
) : RecyclerView.Adapter<AudioContentMainTabContentTagAdapter.ViewHolder>() {
 | 
			
		||||
 | 
			
		||||
    private val tagList = mutableListOf<String>()
 | 
			
		||||
 | 
			
		||||
    private var selectedTag = ""
 | 
			
		||||
 | 
			
		||||
    inner class ViewHolder(
 | 
			
		||||
        private val context: Context,
 | 
			
		||||
        private val binding: ItemContentMainTabContentTagBinding
 | 
			
		||||
    ) : RecyclerView.ViewHolder(binding.root) {
 | 
			
		||||
        @SuppressLint("NotifyDataSetChanged")
 | 
			
		||||
        fun bind(tag: String) {
 | 
			
		||||
            if (tag == selectedTag) {
 | 
			
		||||
                binding.tvTag.setBackgroundResource(
 | 
			
		||||
                    R.drawable.bg_round_corner_2_6_transparent_3bb9f1
 | 
			
		||||
                )
 | 
			
		||||
                binding.tvTag.setTextColor(
 | 
			
		||||
                    ContextCompat.getColor(context, R.color.color_3bb9f1)
 | 
			
		||||
                )
 | 
			
		||||
            } else {
 | 
			
		||||
                binding.tvTag.setBackgroundResource(
 | 
			
		||||
                    R.drawable.bg_round_corner_2_6_transparent_777777
 | 
			
		||||
                )
 | 
			
		||||
                binding.tvTag.setTextColor(
 | 
			
		||||
                    ContextCompat.getColor(context, R.color.color_777777)
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            binding.tvTag.text = tag
 | 
			
		||||
            binding.tvTag.setOnClickListener {
 | 
			
		||||
                selectedTag = tag
 | 
			
		||||
                onClick(tag)
 | 
			
		||||
                notifyDataSetChanged()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
 | 
			
		||||
        parent.context,
 | 
			
		||||
        ItemContentMainTabContentTagBinding.inflate(
 | 
			
		||||
            LayoutInflater.from(parent.context),
 | 
			
		||||
            parent,
 | 
			
		||||
            false
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    override fun getItemCount() = tagList.size
 | 
			
		||||
 | 
			
		||||
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
 | 
			
		||||
        holder.bind(tagList[position])
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SuppressLint("NotifyDataSetChanged")
 | 
			
		||||
    fun addItems(tagList: List<String>) {
 | 
			
		||||
        this.tagList.clear()
 | 
			
		||||
        this.tagList.addAll(tagList)
 | 
			
		||||
 | 
			
		||||
        if (tagList.isNotEmpty()) {
 | 
			
		||||
            selectedTag = tagList[0]
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        notifyDataSetChanged()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -62,6 +62,14 @@ class AudioContentMainTabContentViewModel(
 | 
			
		||||
    val salesCountRankContentListLiveData: LiveData<List<GetAudioContentRankingItem>>
 | 
			
		||||
        get() = _salesCountRankContentListLiveData
 | 
			
		||||
 | 
			
		||||
    private val _tagListLiveData = MutableLiveData<List<String>>()
 | 
			
		||||
    val tagListLiveData: LiveData<List<String>>
 | 
			
		||||
        get() = _tagListLiveData
 | 
			
		||||
 | 
			
		||||
    private val _tagCurationContentListLiveData = MutableLiveData<List<GetAudioContentMainItem>>()
 | 
			
		||||
    val tagCurationContentListLiveData: LiveData<List<GetAudioContentMainItem>>
 | 
			
		||||
        get() = _tagCurationContentListLiveData
 | 
			
		||||
 | 
			
		||||
    fun fetchData() {
 | 
			
		||||
        _isLoading.value = true
 | 
			
		||||
        compositeDisposable.add(
 | 
			
		||||
@@ -87,6 +95,9 @@ class AudioContentMainTabContentViewModel(
 | 
			
		||||
                            _salesCountRankContentListLiveData.value =
 | 
			
		||||
                                data.salesCountRankContentList
 | 
			
		||||
                            _curationListLiveData.value = data.curationList
 | 
			
		||||
 | 
			
		||||
                            _tagListLiveData.value = data.tagList
 | 
			
		||||
                            _tagCurationContentListLiveData.value = data.tagCurationContentList
 | 
			
		||||
                        } else {
 | 
			
		||||
                            if (it.message != null) {
 | 
			
		||||
                                _toastLiveData.postValue(it.message)
 | 
			
		||||
@@ -211,4 +222,38 @@ class AudioContentMainTabContentViewModel(
 | 
			
		||||
                )
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun getRecommendContentByTag(tag: String) {
 | 
			
		||||
        _isLoading.value = true
 | 
			
		||||
        compositeDisposable.add(
 | 
			
		||||
            repository.getRecommendedContentByTag(
 | 
			
		||||
                tag = tag,
 | 
			
		||||
                token = "Bearer ${SharedPreferenceManager.token}"
 | 
			
		||||
            )
 | 
			
		||||
                .subscribeOn(Schedulers.io())
 | 
			
		||||
                .observeOn(AndroidSchedulers.mainThread())
 | 
			
		||||
                .subscribe(
 | 
			
		||||
                    {
 | 
			
		||||
                        if (it.success && it.data != null) {
 | 
			
		||||
                            _tagCurationContentListLiveData.value = it.data!!
 | 
			
		||||
                        } else {
 | 
			
		||||
                            if (it.message != null) {
 | 
			
		||||
                                _toastLiveData.postValue(it.message)
 | 
			
		||||
                            } else {
 | 
			
		||||
                                _toastLiveData.postValue(
 | 
			
		||||
                                    "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
 | 
			
		||||
                                )
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        _isLoading.value = false
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        it.message?.let { message -> Logger.e(message) }
 | 
			
		||||
                        _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
 | 
			
		||||
                        _isLoading.value = false
 | 
			
		||||
                    }
 | 
			
		||||
                )
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -19,5 +19,7 @@ data class GetContentMainTabContentResponse(
 | 
			
		||||
    @SerializedName("contentRankCreatorList") val contentRankCreatorList: List<ContentCreatorResponse>,
 | 
			
		||||
    @SerializedName("salesCountRankContentList") val salesCountRankContentList: List<GetAudioContentRankingItem>,
 | 
			
		||||
    @SerializedName("eventBannerList") val eventBannerList: GetEventResponse,
 | 
			
		||||
    @SerializedName("tagList") val tagList: List<String>,
 | 
			
		||||
    @SerializedName("tagCurationContentList") val tagCurationContentList: List<GetAudioContentMainItem>,
 | 
			
		||||
    @SerializedName("curationList") val curationList: List<GetContentCurationResponse>
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user