콘텐츠 메인

- 무료 탭 UI 구성
This commit is contained in:
klaus 2025-02-14 01:55:39 +09:00
parent b2bf9a4a4a
commit 46ae544cfd
8 changed files with 718 additions and 1 deletions

View File

@ -20,6 +20,7 @@ import kr.co.vividnext.sodalive.audio_content.main.v2.GetPopularContentByCreator
import kr.co.vividnext.sodalive.audio_content.main.v2.alarm.GetContentMainTabAlarmResponse
import kr.co.vividnext.sodalive.audio_content.main.v2.asmr.GetContentMainTabAsmrResponse
import kr.co.vividnext.sodalive.audio_content.main.v2.content.GetContentMainTabContentResponse
import kr.co.vividnext.sodalive.audio_content.main.v2.free.GetContentMainTabLiveFreeResponse
import kr.co.vividnext.sodalive.audio_content.main.v2.home.GetContentMainTabHomeResponse
import kr.co.vividnext.sodalive.audio_content.main.v2.replay.GetContentMainTabLiveReplayResponse
import kr.co.vividnext.sodalive.audio_content.main.v2.series.GetContentMainTabSeriesResponse
@ -300,4 +301,9 @@ interface AudioContentApi {
fun getContentMainReplay(
@Header("Authorization") authHeader: String
): Single<ApiResponse<GetContentMainTabLiveReplayResponse>>
@GET("/v2/audio-content/main/free")
fun getContentMainFree(
@Header("Authorization") authHeader: String
): Single<ApiResponse<GetContentMainTabLiveFreeResponse>>
}

View File

@ -55,7 +55,6 @@ class AudioContentMainTabAsmrViewModel(
if (it.success && it.data != null) {
val data = it.data
Logger.e("data: $data")
_contentBannerLiveData.value = data.contentBannerList
_newContentListLiveData.value = data.newAsmrContentList
_contentRankingLiveData.value = data.rankAsmrContentList

View File

@ -1,9 +1,471 @@
package kr.co.vividnext.sodalive.audio_content.main.v2.free
import android.content.Intent
import android.graphics.Rect
import android.net.Uri
import android.os.Bundle
import android.view.View
import android.widget.LinearLayout
import android.widget.Toast
import androidx.annotation.OptIn
import androidx.core.content.ContextCompat
import androidx.media3.common.util.UnstableApi
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.zhpan.bannerview.BaseBannerAdapter
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.detail.AudioContentDetailActivity
import kr.co.vividnext.sodalive.audio_content.main.AudioContentBannerType
import kr.co.vividnext.sodalive.audio_content.main.AudioContentMainContentAdapter
import kr.co.vividnext.sodalive.audio_content.main.banner.AudioContentMainBannerAdapter
import kr.co.vividnext.sodalive.audio_content.main.new_content.AudioContentMainNewContentThemeAdapter
import kr.co.vividnext.sodalive.audio_content.main.v2.AudioContentMainContentCurationAdapter
import kr.co.vividnext.sodalive.audio_content.main.v2.series.new_series.AudioContentMainNewSeriesAdapter
import kr.co.vividnext.sodalive.audio_content.series.detail.SeriesDetailActivity
import kr.co.vividnext.sodalive.base.BaseFragment
import kr.co.vividnext.sodalive.common.Constants
import kr.co.vividnext.sodalive.common.LoadingDialog
import kr.co.vividnext.sodalive.databinding.FragmentAudioContentMainTabFreeBinding
import kr.co.vividnext.sodalive.explorer.profile.UserProfileActivity
import kr.co.vividnext.sodalive.extensions.dpToPx
import kr.co.vividnext.sodalive.settings.event.EventDetailActivity
import org.koin.android.ext.android.inject
import kotlin.math.roundToInt
@OptIn(UnstableApi::class)
class AudioContentMainTabFreeFragment : BaseFragment<FragmentAudioContentMainTabFreeBinding>(
FragmentAudioContentMainTabFreeBinding::inflate
) {
private val viewModel: AudioContentMainTabFreeViewModel by inject()
private lateinit var loadingDialog: LoadingDialog
private lateinit var contentBannerAdapter: AudioContentMainBannerAdapter
private lateinit var introduceCreatorAdapter: AudioContentMainContentAdapter
private lateinit var recommendSeriesAdapter: AudioContentMainNewSeriesAdapter
private lateinit var newContentThemeAdapter: AudioContentMainNewContentThemeAdapter
private lateinit var newContentAdapter: AudioContentMainContentAdapter
private lateinit var curationAdapter: AudioContentMainContentCurationAdapter
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupView()
bindData()
viewModel.fetchData()
}
private fun setupView() {
loadingDialog = LoadingDialog(requireActivity(), layoutInflater)
setupContentBanner()
setupIntroduceCreator()
setupRecommendSeries()
setupNewContentTheme()
setupNewContent()
setupCuration()
}
private fun setupContentBanner() {
val layoutParams = binding
.rvBanner
.layoutParams as LinearLayout.LayoutParams
val pagerWidth = screenWidth.toDouble() - 26.7f.dpToPx()
val pagerHeight = (pagerWidth * 0.53).roundToInt()
layoutParams.width = pagerWidth.roundToInt()
layoutParams.height = pagerHeight
contentBannerAdapter = AudioContentMainBannerAdapter(
requireContext(),
pagerWidth.roundToInt(),
pagerHeight
) {
when (it.type) {
AudioContentBannerType.EVENT -> {
startActivity(
Intent(requireContext(), EventDetailActivity::class.java).apply {
putExtra(Constants.EXTRA_EVENT, it.eventItem!!)
}
)
}
AudioContentBannerType.CREATOR -> {
startActivity(
Intent(requireContext(), UserProfileActivity::class.java).apply {
putExtra(Constants.EXTRA_USER_ID, it.creatorId!!)
}
)
}
AudioContentBannerType.SERIES -> {
startActivity(
Intent(requireContext(), SeriesDetailActivity::class.java).apply {
putExtra(Constants.EXTRA_SERIES_ID, it.seriesId!!)
}
)
}
AudioContentBannerType.LINK -> {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(it.link!!)))
}
}
}
binding
.rvBanner
.layoutParams = layoutParams
binding.rvBanner.apply {
adapter = contentBannerAdapter as BaseBannerAdapter<Any>
setLifecycleRegistry(lifecycle)
setScrollDuration(1000)
setInterval(4 * 1000)
}.create()
binding
.rvBanner
.setIndicatorView(binding.indicatorBanner)
.setIndicatorStyle(IndicatorStyle.ROUND_RECT)
.setIndicatorSlideMode(IndicatorSlideMode.SMOOTH)
.setIndicatorVisibility(View.GONE)
.setIndicatorSliderColor(
ContextCompat.getColor(requireContext(), R.color.color_909090),
ContextCompat.getColor(requireContext(), R.color.color_3bb9f1)
)
.setIndicatorSliderWidth(4f.dpToPx().toInt(), 10f.dpToPx().toInt())
.setIndicatorHeight(4f.dpToPx().toInt())
viewModel.contentBannerLiveData.observe(viewLifecycleOwner) {
if (contentBannerAdapter.itemCount <= 0 && it.isEmpty()) {
binding.rvBanner.visibility = View.GONE
binding.indicatorBanner.visibility = View.GONE
} else {
binding.rvBanner.visibility = View.VISIBLE
binding.indicatorBanner.visibility = View.VISIBLE
binding.rvBanner.refreshData(it)
}
}
}
private fun setupIntroduceCreator() {
introduceCreatorAdapter = AudioContentMainContentAdapter(
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.rvIntroduceCreator
recyclerView.layoutManager = LinearLayoutManager(
context,
LinearLayoutManager.HORIZONTAL,
false
)
recyclerView.addItemDecoration(object : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
super.getItemOffsets(outRect, view, parent, state)
when (parent.getChildAdapterPosition(view)) {
0 -> {
outRect.left = 0
outRect.right = 6.7f.dpToPx().toInt()
}
introduceCreatorAdapter.itemCount - 1 -> {
outRect.left = 6.7f.dpToPx().toInt()
outRect.right = 0
}
else -> {
outRect.left = 6.7f.dpToPx().toInt()
outRect.right = 6.7f.dpToPx().toInt()
}
}
}
})
recyclerView.adapter = introduceCreatorAdapter
viewModel.introduceCreatorLiveData.observe(viewLifecycleOwner) {
if (it.items.isNotEmpty()) {
binding.llIntroduceCreator.visibility = View.VISIBLE
binding.tvIntroduceCreator.text = it.title
introduceCreatorAdapter.addItems(it.items)
} else {
binding.llIntroduceCreator.visibility = View.GONE
}
}
}
private fun setupRecommendSeries() {
recommendSeriesAdapter = AudioContentMainNewSeriesAdapter(
onClickItem = {
startActivity(
Intent(requireContext(), SeriesDetailActivity::class.java).apply {
putExtra(Constants.EXTRA_SERIES_ID, it)
}
)
},
onClickCreator = {
startActivity(
Intent(requireContext(), UserProfileActivity::class.java).apply {
putExtra(Constants.EXTRA_USER_ID, it)
}
)
}
)
val recyclerView = binding.rvRecommendSeries
recyclerView.layoutManager = LinearLayoutManager(
requireContext(),
LinearLayoutManager.HORIZONTAL,
false
)
recyclerView.addItemDecoration(object : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
super.getItemOffsets(outRect, view, parent, state)
when (parent.getChildAdapterPosition(view)) {
0 -> {
outRect.left = 0
outRect.right = 6.7f.dpToPx().toInt()
}
recommendSeriesAdapter.itemCount - 1 -> {
outRect.right = 0
outRect.left = 6.7f.dpToPx().toInt()
}
else -> {
outRect.left = 6.7f.dpToPx().toInt()
outRect.right = 6.7f.dpToPx().toInt()
}
}
}
})
recyclerView.adapter = recommendSeriesAdapter
viewModel.recommendSeriesListLiveData.observe(viewLifecycleOwner) {
recommendSeriesAdapter.addItems(it)
recyclerView.visibility = if (it.isNotEmpty()) {
View.VISIBLE
} else {
View.GONE
}
}
}
private fun setupNewContentTheme() {
newContentThemeAdapter = AudioContentMainNewContentThemeAdapter {}
binding.rvNewContentTheme.layoutManager = LinearLayoutManager(
context,
LinearLayoutManager.HORIZONTAL,
false
)
binding.rvNewContentTheme.addItemDecoration(object : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
super.getItemOffsets(outRect, view, parent, state)
when (parent.getChildAdapterPosition(view)) {
0 -> {
outRect.left = 0
outRect.right = 4f.dpToPx().toInt()
}
newContentThemeAdapter.itemCount - 1 -> {
outRect.left = 4f.dpToPx().toInt()
outRect.right = 0
}
else -> {
outRect.left = 4f.dpToPx().toInt()
outRect.right = 4f.dpToPx().toInt()
}
}
}
})
binding.rvNewContentTheme.adapter = newContentThemeAdapter
viewModel.themeListLiveData.observe(viewLifecycleOwner) {
binding.llNewContent.visibility = View.VISIBLE
newContentThemeAdapter.addItems(it)
}
}
private fun setupNewContent() {
binding.ivNewContentAll.setOnClickListener {
startActivity(Intent(requireContext(), AudioContentNewAllActivity::class.java))
}
newContentAdapter = AudioContentMainContentAdapter(
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)
}
)
}
)
binding.rvNewContent.layoutManager = LinearLayoutManager(
context,
LinearLayoutManager.HORIZONTAL,
false
)
binding.rvNewContent.addItemDecoration(object : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
super.getItemOffsets(outRect, view, parent, state)
when (parent.getChildAdapterPosition(view)) {
0 -> {
outRect.left = 0
outRect.right = 6.7f.dpToPx().toInt()
}
newContentAdapter.itemCount - 1 -> {
outRect.left = 6.7f.dpToPx().toInt()
outRect.right = 0
}
else -> {
outRect.left = 6.7f.dpToPx().toInt()
outRect.right = 6.7f.dpToPx().toInt()
}
}
}
})
binding.rvNewContent.adapter = newContentAdapter
viewModel.newContentListLiveData.observe(viewLifecycleOwner) {
newContentAdapter.addItems(it)
}
}
private fun setupCuration() {
curationAdapter = AudioContentMainContentCurationAdapter(
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)
}
)
}
)
binding.rvCuration.layoutManager = LinearLayoutManager(
context,
LinearLayoutManager.VERTICAL,
false
)
binding.rvCuration.addItemDecoration(object : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
super.getItemOffsets(outRect, view, parent, state)
when (parent.getChildAdapterPosition(view)) {
0 -> {
outRect.top = 30f.dpToPx().toInt()
outRect.bottom = 15f.dpToPx().toInt()
}
curationAdapter.itemCount - 1 -> {
outRect.top = 15f.dpToPx().toInt()
outRect.bottom = 30f.dpToPx().toInt()
}
else -> {
outRect.top = 15f.dpToPx().toInt()
outRect.bottom = 15f.dpToPx().toInt()
}
}
}
})
binding.rvCuration.adapter = curationAdapter
viewModel.curationListLiveData.observe(viewLifecycleOwner) {
curationAdapter.addItems(it)
binding.rvCuration.visibility = if (curationAdapter.itemCount <= 0 && it.isEmpty()) {
View.GONE
} else {
View.VISIBLE
}
}
}
private fun bindData() {
viewModel.toastLiveData.observe(viewLifecycleOwner) {
it?.let { Toast.makeText(requireContext(), it, Toast.LENGTH_LONG).show() }
}
viewModel.isLoading.observe(viewLifecycleOwner) {
if (it) {
loadingDialog.show(screenWidth)
} else {
loadingDialog.dismiss()
}
}
}
}

View File

@ -0,0 +1,7 @@
package kr.co.vividnext.sodalive.audio_content.main.v2.free
import kr.co.vividnext.sodalive.audio_content.AudioContentApi
class AudioContentMainTabFreeRepository(private val api: AudioContentApi) {
fun getContentMainFree(token: String) = api.getContentMainFree(authHeader = token)
}

View File

@ -0,0 +1,95 @@
package kr.co.vividnext.sodalive.audio_content.main.v2.free
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.orhanobut.logger.Logger
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.schedulers.Schedulers
import kr.co.vividnext.sodalive.audio_content.main.GetAudioContentBannerResponse
import kr.co.vividnext.sodalive.audio_content.main.GetAudioContentMainItem
import kr.co.vividnext.sodalive.audio_content.main.v2.GetContentCurationResponse
import kr.co.vividnext.sodalive.audio_content.main.v2.series.GetRecommendSeriesListResponse
import kr.co.vividnext.sodalive.base.BaseViewModel
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
class AudioContentMainTabFreeViewModel(
private val repository: AudioContentMainTabFreeRepository
) : BaseViewModel() {
private val _toastLiveData = MutableLiveData<String?>()
val toastLiveData: LiveData<String?>
get() = _toastLiveData
private var _isLoading = MutableLiveData(false)
val isLoading: LiveData<Boolean>
get() = _isLoading
private var _contentBannerLiveData = MutableLiveData<List<GetAudioContentBannerResponse>>()
val contentBannerLiveData: LiveData<List<GetAudioContentBannerResponse>>
get() = _contentBannerLiveData
private var _introduceCreatorLiveData = MutableLiveData<GetContentCurationResponse>()
val introduceCreatorLiveData: LiveData<GetContentCurationResponse>
get() = _introduceCreatorLiveData
private var _recommendSeriesListLiveData =
MutableLiveData<List<GetRecommendSeriesListResponse>>()
val recommendSeriesListLiveData: LiveData<List<GetRecommendSeriesListResponse>>
get() = _recommendSeriesListLiveData
private var _newContentListLiveData = MutableLiveData<List<GetAudioContentMainItem>>()
val newContentListLiveData: LiveData<List<GetAudioContentMainItem>>
get() = _newContentListLiveData
private var _themeListLiveData = MutableLiveData<List<String>>()
val themeListLiveData: LiveData<List<String>>
get() = _themeListLiveData
private var _curationListLiveData = MutableLiveData<List<GetContentCurationResponse>>()
val curationListLiveData: LiveData<List<GetContentCurationResponse>>
get() = _curationListLiveData
fun fetchData() {
_isLoading.value = true
compositeDisposable.add(
repository.getContentMainFree(token = "Bearer ${SharedPreferenceManager.token}")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
if (it.success && it.data != null) {
val data = it.data
_contentBannerLiveData.value = data.contentBannerList
if (data.introduceCreator != null) {
_introduceCreatorLiveData.value = data.introduceCreator!!
}
_recommendSeriesListLiveData.value = data.recommendSeriesList
_newContentListLiveData.value = data.newFreeContentList
val themeList = listOf("전체").union(data.themeList).toList()
_themeListLiveData.value = themeList
_curationListLiveData.value = data.curationList
} else {
if (it.message != null) {
_toastLiveData.postValue(it.message)
} else {
_toastLiveData.postValue(
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
)
}
}
_isLoading.value = false
},
{
_isLoading.value = false
it.message?.let { message -> Logger.e(message) }
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
}
)
)
}
}

View File

@ -0,0 +1,24 @@
package kr.co.vividnext.sodalive.audio_content.main.v2.free
import androidx.annotation.Keep
import com.google.gson.annotations.SerializedName
import kr.co.vividnext.sodalive.audio_content.main.GetAudioContentBannerResponse
import kr.co.vividnext.sodalive.audio_content.main.GetAudioContentMainItem
import kr.co.vividnext.sodalive.audio_content.main.v2.GetContentCurationResponse
import kr.co.vividnext.sodalive.audio_content.main.v2.series.GetRecommendSeriesListResponse
@Keep
data class GetContentMainTabLiveFreeResponse(
@SerializedName("contentBannerList")
val contentBannerList: List<GetAudioContentBannerResponse>,
@SerializedName("introduceCreator")
val introduceCreator: GetContentCurationResponse?,
@SerializedName("recommendSeriesList")
val recommendSeriesList: List<GetRecommendSeriesListResponse>,
@SerializedName("themeList")
val themeList: List<String>,
@SerializedName("newFreeContentList")
val newFreeContentList: List<GetAudioContentMainItem>,
@SerializedName("curationList")
val curationList: List<GetContentCurationResponse>
)

View File

@ -29,6 +29,8 @@ import kr.co.vividnext.sodalive.audio_content.main.v2.asmr.AudioContentMainTabAs
import kr.co.vividnext.sodalive.audio_content.main.v2.asmr.AudioContentMainTabAsmrViewModel
import kr.co.vividnext.sodalive.audio_content.main.v2.content.AudioContentMainTabContentRepository
import kr.co.vividnext.sodalive.audio_content.main.v2.content.AudioContentMainTabContentViewModel
import kr.co.vividnext.sodalive.audio_content.main.v2.free.AudioContentMainTabFreeRepository
import kr.co.vividnext.sodalive.audio_content.main.v2.free.AudioContentMainTabFreeViewModel
import kr.co.vividnext.sodalive.audio_content.main.v2.home.AudioContentMainTabHomeRepository
import kr.co.vividnext.sodalive.audio_content.main.v2.home.AudioContentMainTabHomeViewModel
import kr.co.vividnext.sodalive.audio_content.main.v2.replay.AudioContentMainTabReplayRepository
@ -308,6 +310,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
viewModel { AudioContentMainTabAlarmViewModel(get()) }
viewModel { AudioContentMainTabAsmrViewModel(get()) }
viewModel { AudioContentMainTabReplayViewModel(get()) }
viewModel { AudioContentMainTabFreeViewModel(get()) }
}
private val repositoryModule = module {
@ -345,6 +348,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
factory { AudioContentMainTabAlarmRepository(get()) }
factory { AudioContentMainTabAsmrRepository(get()) }
factory { AudioContentMainTabReplayRepository(get()) }
factory { AudioContentMainTabFreeRepository(get()) }
}
private val moduleList = listOf(

View File

@ -21,5 +21,125 @@
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="6.7dp" />
<LinearLayout
android:id="@+id/ll_introduce_creator"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:orientation="vertical"
android:visibility="gone">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_introduce_creator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="13.3dp"
android:fontFamily="@font/gmarket_sans_bold"
android:text="크리에이터 소개"
android:textColor="@color/color_eeeeee"
android:textSize="18.3sp" />
<ImageView
android:id="@+id/iv_introduce_creator_all"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:contentDescription="@null"
android:paddingHorizontal="13.3dp"
android:src="@drawable/ic_forward" />
</RelativeLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_introduce_creator"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:clipToPadding="false"
android:paddingHorizontal="13.3dp" />
</LinearLayout>
<LinearLayout
android:id="@+id/ll_recommend_series"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="13.3dp"
android:fontFamily="@font/gmarket_sans_bold"
android:text="추천 무료 시리즈"
android:textColor="@color/color_eeeeee"
android:textSize="18.3sp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_recommend_series"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="13.3dp"
android:clipToPadding="false"
android:paddingHorizontal="13.3dp" />
</LinearLayout>
<LinearLayout
android:id="@+id/ll_new_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:orientation="vertical"
android:visibility="gone">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="13.3dp"
android:fontFamily="@font/gmarket_sans_bold"
android:text="새로운 무료 콘텐츠"
android:textColor="@color/color_eeeeee"
android:textSize="18.3sp" />
<ImageView
android:id="@+id/iv_new_content_all"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:contentDescription="@null"
android:paddingHorizontal="13.3dp"
android:src="@drawable/ic_forward" />
</RelativeLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_new_content_theme"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="13.3dp"
android:clipToPadding="false"
android:paddingHorizontal="13.3dp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_new_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="13.3dp"
android:clipToPadding="false"
android:paddingHorizontal="13.3dp" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_curation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>