콘텐츠 메인 무료 탭
- 채널별 추천 무료 콘텐츠 UI 추가
This commit is contained in:
		| @@ -368,4 +368,10 @@ interface AudioContentApi { | ||||
|         @Query("size") size: Int, | ||||
|         @Header("Authorization") authHeader: String | ||||
|     ): Single<ApiResponse<List<GetAudioContentMainItem>>> | ||||
|  | ||||
|     @GET("/v2/audio-content/main/free/popular-content-by-creator") | ||||
|     fun getPopularFreeContentByCreator( | ||||
|         @Query("creatorId") creatorId: Long, | ||||
|         @Header("Authorization") authHeader: String | ||||
|     ): Single<ApiResponse<List<GetAudioContentRankingItem>>> | ||||
| } | ||||
|   | ||||
| @@ -10,6 +10,7 @@ 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 | ||||
| import com.zhpan.bannerview.BaseBannerAdapter | ||||
| @@ -23,11 +24,14 @@ import kr.co.vividnext.sodalive.audio_content.main.AudioContentMainContentAdapte | ||||
| 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.ContentRankCreatorAdapter | ||||
| import kr.co.vividnext.sodalive.audio_content.main.v2.PopularContentByCreatorAdapter | ||||
| import kr.co.vividnext.sodalive.audio_content.main.v2.free.introduce_creator.IntroduceCreatorActivity | ||||
| 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.GridSpacingItemDecoration | ||||
| import kr.co.vividnext.sodalive.common.LoadingDialog | ||||
| import kr.co.vividnext.sodalive.databinding.FragmentAudioContentMainTabFreeBinding | ||||
| import kr.co.vividnext.sodalive.explorer.profile.UserProfileActivity | ||||
| @@ -49,6 +53,8 @@ class AudioContentMainTabFreeFragment : BaseFragment<FragmentAudioContentMainTab | ||||
|     private lateinit var newContentThemeAdapter: AudioContentMainNewContentThemeAdapter | ||||
|     private lateinit var newContentAdapter: AudioContentMainContentAdapter | ||||
|     private lateinit var curationAdapter: AudioContentMainContentCurationAdapter | ||||
|     private lateinit var contentRankCreatorAdapter: ContentRankCreatorAdapter | ||||
|     private lateinit var popularContentByCreatorAdapter: PopularContentByCreatorAdapter | ||||
|  | ||||
|     override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | ||||
|         super.onViewCreated(view, savedInstanceState) | ||||
| @@ -66,6 +72,8 @@ class AudioContentMainTabFreeFragment : BaseFragment<FragmentAudioContentMainTab | ||||
|         setupRecommendSeries() | ||||
|         setupNewContentTheme() | ||||
|         setupNewContent() | ||||
|         setupPopularContentCreator() | ||||
|         setupPopularContentByCreator() | ||||
|         setupCuration() | ||||
|     } | ||||
|  | ||||
| @@ -281,7 +289,7 @@ class AudioContentMainTabFreeFragment : BaseFragment<FragmentAudioContentMainTab | ||||
|  | ||||
|         viewModel.recommendSeriesListLiveData.observe(viewLifecycleOwner) { | ||||
|             recommendSeriesAdapter.addItems(it) | ||||
|             recyclerView.visibility = if (it.isNotEmpty()) { | ||||
|             binding.llRecommendSeries.visibility = if (it.isNotEmpty()) { | ||||
|                 View.VISIBLE | ||||
|             } else { | ||||
|                 View.GONE | ||||
| @@ -406,6 +414,102 @@ class AudioContentMainTabFreeFragment : BaseFragment<FragmentAudioContentMainTab | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun setupPopularContentCreator() { | ||||
|         contentRankCreatorAdapter = ContentRankCreatorAdapter { | ||||
|             binding.llNoItems.visibility = View.VISIBLE | ||||
|             binding.rvRankingPlayCount.visibility = View.GONE | ||||
|             viewModel.getPopularContentByCreator(it) | ||||
|         } | ||||
|  | ||||
|         binding.rvRankingCreator.layoutManager = LinearLayoutManager( | ||||
|             context, | ||||
|             LinearLayoutManager.HORIZONTAL, | ||||
|             false | ||||
|         ) | ||||
|  | ||||
|         binding.rvRankingCreator.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 = 11f.dpToPx().toInt() | ||||
|                     } | ||||
|  | ||||
|                     contentRankCreatorAdapter.itemCount - 1 -> { | ||||
|                         outRect.left = 11f.dpToPx().toInt() | ||||
|                         outRect.right = 0 | ||||
|                     } | ||||
|  | ||||
|                     else -> { | ||||
|                         outRect.left = 11f.dpToPx().toInt() | ||||
|                         outRect.right = 11f.dpToPx().toInt() | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         }) | ||||
|  | ||||
|         binding.rvRankingCreator.adapter = contentRankCreatorAdapter | ||||
|  | ||||
|         viewModel.contentCreatorListLiveData.observe(viewLifecycleOwner) { | ||||
|             contentRankCreatorAdapter.addItems(it) | ||||
|             if (contentRankCreatorAdapter.itemCount <= 0 && it.isEmpty()) { | ||||
|                 binding.llCreatorContentRanking.visibility = View.GONE | ||||
|             } else { | ||||
|                 binding.llCreatorContentRanking.visibility = View.VISIBLE | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun setupPopularContentByCreator() { | ||||
|         popularContentByCreatorAdapter = PopularContentByCreatorAdapter( | ||||
|             itemWidth = ((screenWidth - 13.3f.dpToPx() * 3) / 2).toInt(), | ||||
|             onClickItem = { contentId -> | ||||
|                 startActivity( | ||||
|                     Intent(requireActivity(), AudioContentDetailActivity::class.java).apply { | ||||
|                         putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, contentId) | ||||
|                     } | ||||
|                 ) | ||||
|             }, | ||||
|             onClickCreator = { creatorId -> | ||||
|                 startActivity( | ||||
|                     Intent(requireActivity(), UserProfileActivity::class.java).apply { | ||||
|                         putExtra(Constants.EXTRA_USER_ID, creatorId) | ||||
|                     } | ||||
|                 ) | ||||
|             } | ||||
|         ) | ||||
|  | ||||
|         val recyclerView = binding.rvRankingPlayCount | ||||
|         recyclerView.layoutManager = GridLayoutManager(requireContext(), 2) | ||||
|         recyclerView.addItemDecoration( | ||||
|             GridSpacingItemDecoration( | ||||
|                 2, | ||||
|                 13.3f.dpToPx().toInt(), | ||||
|                 false | ||||
|             ) | ||||
|         ) | ||||
|  | ||||
|         recyclerView.adapter = popularContentByCreatorAdapter | ||||
|  | ||||
|         viewModel.playCountRankContentListLiveData.observe(viewLifecycleOwner) { | ||||
|             if (it.isNotEmpty()) { | ||||
|                 binding.llNoItems.visibility = View.GONE | ||||
|                 recyclerView.visibility = View.VISIBLE | ||||
|                 popularContentByCreatorAdapter.addItems(it) | ||||
|             } else { | ||||
|                 binding.llNoItems.visibility = View.VISIBLE | ||||
|                 recyclerView.visibility = View.GONE | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun setupCuration() { | ||||
|         curationAdapter = AudioContentMainContentCurationAdapter( | ||||
|             onClickItem = { | ||||
|   | ||||
| @@ -21,4 +21,9 @@ class AudioContentMainTabFreeRepository(private val api: AudioContentApi) { | ||||
|         size = size, | ||||
|         authHeader = token | ||||
|     ) | ||||
|  | ||||
|     fun getPopularContentByCreator( | ||||
|         creatorId: Long, | ||||
|         token: String | ||||
|     ) = api.getPopularFreeContentByCreator(creatorId, authHeader = token) | ||||
| } | ||||
|   | ||||
| @@ -5,8 +5,10 @@ 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.ContentCreatorResponse | ||||
| 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.GetAudioContentRankingItem | ||||
| 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 | ||||
| @@ -45,6 +47,15 @@ class AudioContentMainTabFreeViewModel( | ||||
|     val themeListLiveData: LiveData<List<String>> | ||||
|         get() = _themeListLiveData | ||||
|  | ||||
|     private val _contentCreatorListLiveData = MutableLiveData<List<ContentCreatorResponse>>() | ||||
|     val contentCreatorListLiveData: LiveData<List<ContentCreatorResponse>> | ||||
|         get() = _contentCreatorListLiveData | ||||
|  | ||||
|     private val _playCountRankContentListLiveData = | ||||
|         MutableLiveData<List<GetAudioContentRankingItem>>() | ||||
|     val playCountRankContentListLiveData: LiveData<List<GetAudioContentRankingItem>> | ||||
|         get() = _playCountRankContentListLiveData | ||||
|  | ||||
|     private var _curationListLiveData = MutableLiveData<List<GetContentCurationResponse>>() | ||||
|     val curationListLiveData: LiveData<List<GetContentCurationResponse>> | ||||
|         get() = _curationListLiveData | ||||
| @@ -71,6 +82,10 @@ class AudioContentMainTabFreeViewModel( | ||||
|                             val themeList = listOf("전체").union(data.themeList).toList() | ||||
|                             _themeListLiveData.value = themeList | ||||
|  | ||||
|                             _contentCreatorListLiveData.value = data.creatorList | ||||
|                             _playCountRankContentListLiveData.value = | ||||
|                                 data.playCountRankContentList | ||||
|  | ||||
|                             _curationListLiveData.value = data.curationList | ||||
|                         } else { | ||||
|                             if (it.message != null) { | ||||
| @@ -130,4 +145,37 @@ class AudioContentMainTabFreeViewModel( | ||||
|                 ) | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     fun getPopularContentByCreator(creatorId: Long) { | ||||
|         _isLoading.value = true | ||||
|         compositeDisposable.add( | ||||
|             repository.getPopularContentByCreator( | ||||
|                 creatorId = creatorId, | ||||
|                 token = "Bearer ${SharedPreferenceManager.token}" | ||||
|             ) | ||||
|                 .subscribeOn(Schedulers.io()) | ||||
|                 .observeOn(AndroidSchedulers.mainThread()) | ||||
|                 .subscribe( | ||||
|                     { | ||||
|                         _isLoading.value = false | ||||
|                         if (it.success && it.data != null) { | ||||
|                             _playCountRankContentListLiveData.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("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.") | ||||
|                     } | ||||
|                 ) | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -2,8 +2,10 @@ 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.ContentCreatorResponse | ||||
| 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.GetAudioContentRankingItem | ||||
| import kr.co.vividnext.sodalive.audio_content.main.v2.GetContentCurationResponse | ||||
| import kr.co.vividnext.sodalive.audio_content.main.v2.series.GetRecommendSeriesListResponse | ||||
|  | ||||
| @@ -19,6 +21,10 @@ data class GetContentMainTabLiveFreeResponse( | ||||
|     val themeList: List<String>, | ||||
|     @SerializedName("newFreeContentList") | ||||
|     val newFreeContentList: List<GetAudioContentMainItem>, | ||||
|     @SerializedName("creatorList") | ||||
|     val creatorList: List<ContentCreatorResponse>, | ||||
|     @SerializedName("playCountRankContentList") | ||||
|     val playCountRankContentList: List<GetAudioContentRankingItem>, | ||||
|     @SerializedName("curationList") | ||||
|     val curationList: List<GetContentCurationResponse> | ||||
| ) | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:tools="http://schemas.android.com/tools" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="match_parent"> | ||||
|  | ||||
| @@ -68,7 +69,8 @@ | ||||
|             android:layout_width="match_parent" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_marginTop="30dp" | ||||
|             android:orientation="vertical"> | ||||
|             android:orientation="vertical" | ||||
|             android:visibility="gone"> | ||||
|  | ||||
|             <TextView | ||||
|                 android:layout_width="wrap_content" | ||||
| @@ -136,6 +138,70 @@ | ||||
|                 android:paddingHorizontal="13.3dp" /> | ||||
|         </LinearLayout> | ||||
|  | ||||
|         <LinearLayout | ||||
|             android:id="@+id/ll_creator_content_ranking" | ||||
|             android:layout_width="match_parent" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_marginTop="30dp" | ||||
|             android:orientation="vertical" | ||||
|             android:visibility="gone"> | ||||
|  | ||||
|             <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_ranking_creator" | ||||
|                 android:layout_width="match_parent" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:layout_marginTop="20dp" | ||||
|                 android:clipToPadding="false" | ||||
|                 android:paddingHorizontal="13.3dp" /> | ||||
|  | ||||
|             <androidx.recyclerview.widget.RecyclerView | ||||
|                 android:id="@+id/rv_ranking_play_count" | ||||
|                 android:layout_width="match_parent" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:layout_marginTop="20dp" | ||||
|                 android:clipToPadding="false" | ||||
|                 android:paddingHorizontal="13.3dp" /> | ||||
|  | ||||
|             <LinearLayout | ||||
|                 android:id="@+id/ll_no_items" | ||||
|                 android:layout_width="match_parent" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:layout_marginHorizontal="13.3dp" | ||||
|                 android:layout_marginTop="28.3dp" | ||||
|                 android:background="@drawable/bg_round_corner_4_7_13181b" | ||||
|                 android:gravity="center" | ||||
|                 android:orientation="vertical" | ||||
|                 android:paddingVertical="16.7dp"> | ||||
|  | ||||
|                 <ImageView | ||||
|                     android:layout_width="wrap_content" | ||||
|                     android:layout_height="wrap_content" | ||||
|                     android:contentDescription="@null" | ||||
|                     android:src="@drawable/ic_no_item" /> | ||||
|  | ||||
|                 <TextView | ||||
|                     android:layout_width="wrap_content" | ||||
|                     android:layout_height="wrap_content" | ||||
|                     android:layout_marginVertical="10dp" | ||||
|                     android:fontFamily="@font/gmarket_sans_medium" | ||||
|                     android:gravity="center" | ||||
|                     android:lineSpacingExtra="8dp" | ||||
|                     android:text="마이페이지에서 본인인증을 해주세요" | ||||
|                     android:textColor="@color/color_bbbbbb" | ||||
|                     android:textSize="13sp" | ||||
|                     tools:ignore="SmallSp" /> | ||||
|             </LinearLayout> | ||||
|         </LinearLayout> | ||||
|  | ||||
|         <androidx.recyclerview.widget.RecyclerView | ||||
|             android:id="@+id/rv_curation" | ||||
|             android:layout_width="match_parent" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user