콘텐츠 메인 단편 탭
- 태그별 추천 콘텐츠 영역 추가
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> | ||||
| ) | ||||
|   | ||||
| @@ -0,0 +1,8 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <shape xmlns:android="http://schemas.android.com/apk/res/android"> | ||||
|     <solid android:color="@android:color/transparent" /> | ||||
|     <corners android:radius="2.6dp" /> | ||||
|     <stroke | ||||
|         android:width="1dp" | ||||
|         android:color="@color/color_3bb9f1" /> | ||||
| </shape> | ||||
| @@ -0,0 +1,8 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <shape xmlns:android="http://schemas.android.com/apk/res/android"> | ||||
|     <solid android:color="@android:color/transparent" /> | ||||
|     <corners android:radius="2.6dp" /> | ||||
|     <stroke | ||||
|         android:width="1dp" | ||||
|         android:color="@color/color_777777" /> | ||||
| </shape> | ||||
| @@ -185,6 +185,40 @@ | ||||
|             android:layout_marginTop="6.7dp" | ||||
|             android:visibility="gone" /> | ||||
|  | ||||
|         <LinearLayout | ||||
|             android:id="@+id/ll_recommend_content_by_tag" | ||||
|             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_content_tag" | ||||
|                 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_recommend_content" | ||||
|                 android:layout_width="match_parent" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:layout_marginTop="6.7dp" | ||||
|                 android:clipToPadding="false" | ||||
|                 android:paddingHorizontal="6.7dp" | ||||
|                 android:paddingBottom="6.7dp" /> | ||||
|         </LinearLayout> | ||||
|  | ||||
|         <androidx.recyclerview.widget.RecyclerView | ||||
|             android:id="@+id/rv_curation" | ||||
|             android:layout_width="match_parent" | ||||
|   | ||||
| @@ -0,0 +1,18 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:tools="http://schemas.android.com/tools" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="wrap_content"> | ||||
|  | ||||
|     <TextView | ||||
|         android:id="@+id/tv_tag" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_gravity="center" | ||||
|         android:background="@drawable/bg_round_corner_2_6_transparent_777777" | ||||
|         android:gravity="center" | ||||
|         android:paddingVertical="10dp" | ||||
|         android:textColor="@color/color_777777" | ||||
|         android:textSize="12sp" | ||||
|         tools:text="자작곡" /> | ||||
| </FrameLayout> | ||||
		Reference in New Issue
	
	Block a user