From a15b478ac68720c54f0ed8c9feb5b2cfbf0c0d60 Mon Sep 17 00:00:00 2001 From: klaus Date: Thu, 20 Nov 2025 02:32:42 +0900 Subject: [PATCH] =?UTF-8?q?feat(audio-content-all):=20theme=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sodalive/audio_content/AudioContentApi.kt | 11 ++++ .../audio_content/AudioContentRepository.kt | 27 +++++++-- .../audio_content/AudioContentViewModel.kt | 5 +- .../all/AudioContentAllActivity.kt | 60 ++++++++++++++++++- .../all/AudioContentAllViewModel.kt | 55 +++++++++++++++++ .../res/layout/activity_audio_content_all.xml | 45 ++++++++++++-- ...m_audio_content_main_new_content_theme.xml | 7 ++- 7 files changed, 195 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentApi.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentApi.kt index 17c5632a..d656af9f 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentApi.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentApi.kt @@ -45,6 +45,8 @@ interface AudioContentApi { @Query("size") size: Int, @Query("isFree") isFree: Boolean?, @Query("isPointAvailableOnly") isPointAvailableOnly: Boolean?, + @Query("sort-type") sortType: AudioContentViewModel.Sort = AudioContentViewModel.Sort.NEWEST, + @Query("theme") theme: String? = null, @Header("Authorization") authHeader: String ): Single>> @@ -71,6 +73,15 @@ interface AudioContentApi { @Header("Authorization") authHeader: String ): Single>> + @GET("/audio-content/theme/active") + fun getAudioContentActiveThemeList( + @Query("isAdultContentVisible") isAdultContentVisible: Boolean, + @Query("contentType") contentType: ContentType, + @Query("isFree") isFree: Boolean?, + @Query("isPointAvailableOnly") isPointAvailableOnly: Boolean?, + @Header("Authorization") authHeader: String + ): Single>> + @GET("/audio-content/theme/{id}/content") fun getAudioContentByTheme( @Path("id") id: Long, diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentRepository.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentRepository.kt index b6bc8554..5862c7fd 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentRepository.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentRepository.kt @@ -34,7 +34,7 @@ class AudioContentRepository( fun getAudioContentReplayLiveList(token: String) = api.getAudioContentReplayLiveList( isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible, - contentType = ContentType.values()[SharedPreferenceManager.contentPreference], + contentType = ContentType.entries[SharedPreferenceManager.contentPreference], authHeader = token ) @@ -119,7 +119,7 @@ class AudioContentRepository( isFree = isFree, theme = theme, isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible, - contentType = ContentType.values()[SharedPreferenceManager.contentPreference], + contentType = ContentType.entries[SharedPreferenceManager.contentPreference], page = page - 1, size = size, authHeader = token @@ -127,7 +127,7 @@ class AudioContentRepository( fun getNewContentThemeList(token: String) = api.getNewContentThemeList( isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible, - contentType = ContentType.values()[SharedPreferenceManager.contentPreference], + contentType = ContentType.entries[SharedPreferenceManager.contentPreference], authHeader = token ) @@ -183,25 +183,42 @@ class AudioContentRepository( ) = api.getAudioContentByTheme( id = themeId, isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible, - contentType = ContentType.values()[SharedPreferenceManager.contentPreference], + contentType = ContentType.entries[SharedPreferenceManager.contentPreference], page = page - 1, size = size, sort = sort, authHeader = token ) + fun getAllAudioContents( page: Int, size: Int, isFree: Boolean? = null, isPointAvailableOnly: Boolean? = null, + sortType: AudioContentViewModel.Sort = AudioContentViewModel.Sort.NEWEST, + theme: String? = null, token: String ) = api.getAllAudioContents( isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible, - contentType = ContentType.values()[SharedPreferenceManager.contentPreference], + contentType = ContentType.entries[SharedPreferenceManager.contentPreference], page = page - 1, size = size, isFree = isFree, isPointAvailableOnly = isPointAvailableOnly, + sortType = sortType, + theme = theme, + authHeader = token + ) + + fun getAudioContentActiveThemeList( + isFree: Boolean? = null, + isPointAvailableOnly: Boolean? = null, + token: String + ) = api.getAudioContentActiveThemeList( + isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible, + contentType = ContentType.entries[SharedPreferenceManager.contentPreference], + isFree = isFree, + isPointAvailableOnly = isPointAvailableOnly, authHeader = token ) } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentViewModel.kt index 186fd6a5..082fea3c 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentViewModel.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/AudioContentViewModel.kt @@ -41,7 +41,10 @@ class AudioContentViewModel(private val repository: AudioContentRepository) : Ba PRICE_HIGH, @SerializedName("PRICE_LOW") - PRICE_LOW + PRICE_LOW, + + @SerializedName("POPULARITY") + POPULARITY } var isLast = false diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/all/AudioContentAllActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/all/AudioContentAllActivity.kt index e4450793..d19d3f2a 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/all/AudioContentAllActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/all/AudioContentAllActivity.kt @@ -1,6 +1,7 @@ package kr.co.vividnext.sodalive.audio_content.all import android.content.Intent +import android.graphics.Rect import android.os.Bundle import android.view.View import android.widget.Toast @@ -8,6 +9,7 @@ import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity +import kr.co.vividnext.sodalive.audio_content.main.new_content.AudioContentMainNewContentThemeAdapter import kr.co.vividnext.sodalive.base.BaseActivity import kr.co.vividnext.sodalive.common.Constants import kr.co.vividnext.sodalive.common.GridSpacingItemDecoration @@ -25,6 +27,7 @@ class AudioContentAllActivity : BaseActivity( private lateinit var loadingDialog: LoadingDialog private lateinit var adapter: HomeContentAdapter + private lateinit var themeAdapter: AudioContentMainNewContentThemeAdapter private var isFree: Boolean = false private var isPointOnly: Boolean = false @@ -36,6 +39,10 @@ class AudioContentAllActivity : BaseActivity( bindData() viewModel.reset() + viewModel.getThemeList( + isFree = if (isFree) true else null, + isPointAvailableOnly = if (isPointOnly) true else null + ) viewModel.loadAll( isFree = if (isFree) true else null, isPointAvailableOnly = if (isPointOnly) true else null @@ -51,12 +58,57 @@ class AudioContentAllActivity : BaseActivity( } binding.toolbar.tvBack.setOnClickListener { finish() } + setupTheme() setupRecycler() } + private fun setupTheme() { + themeAdapter = AudioContentMainNewContentThemeAdapter { + adapter.addItems(emptyList()) + viewModel.selectTheme(it, isFree = isFree, isPointOnly = isPointOnly) + } + + binding.rvTheme.layoutManager = LinearLayoutManager( + this, + LinearLayoutManager.HORIZONTAL, + false + ) + + binding.rvTheme.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() + } + + themeAdapter.itemCount - 1 -> { + outRect.left = 4f.dpToPx().toInt() + outRect.right = 0 + } + + else -> { + outRect.left = 4f.dpToPx().toInt() + outRect.right = 4f.dpToPx().toInt() + } + } + } + }) + + binding.rvTheme.adapter = themeAdapter + } + private fun setupRecycler() { - // 아이템 정사각형 크기 계산: (screenWidth - (24*2) - 16) / 2 - val itemSize = ((screenWidth - 24f.dpToPx() * 2 - 16f.dpToPx()) / 2f).toInt() + // 아이템 정사각형 크기 계산: (screenWidth - (16*2) - 16) / 2 + // 아이템 정사각형 크기 계산: (screenWidth - (paddingHorizontal*2) - itemSpacing) / 2 + val itemSize = ((screenWidth - 16f.dpToPx() * 2 - 16f.dpToPx()) / 2f).toInt() adapter = HomeContentAdapter( onClickItem = { @@ -104,6 +156,10 @@ class AudioContentAllActivity : BaseActivity( it?.let { Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show() } } + viewModel.themeListLiveData.observe(this) { + themeAdapter.addItems(it) + } + viewModel.itemsLiveData.observe(this) { list -> if (adapter.itemCount > 0 || list.isNotEmpty()) { binding.rvContent.visibility = View.VISIBLE diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/all/AudioContentAllViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/all/AudioContentAllViewModel.kt index 2817ab38..2063e945 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/all/AudioContentAllViewModel.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/all/AudioContentAllViewModel.kt @@ -2,6 +2,7 @@ package kr.co.vividnext.sodalive.audio_content.all 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.AudioContentRepository @@ -22,15 +23,58 @@ class AudioContentAllViewModel( private val _itemsLiveData = MutableLiveData>() val itemsLiveData: LiveData> get() = _itemsLiveData + private var _themeListLiveData = MutableLiveData>() + val themeListLiveData: LiveData> + get() = _themeListLiveData + private var page = 1 private val size = 20 private var isLast = false + private var selectedTheme = "전체" fun reset() { page = 1 isLast = false } + fun getThemeList( + isFree: Boolean? = null, + isPointAvailableOnly: Boolean? = null + ) { + compositeDisposable.add( + repository.getAudioContentActiveThemeList( + isFree = isFree, + isPointAvailableOnly = isPointAvailableOnly, + token = "Bearer ${SharedPreferenceManager.token}" + ) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + if (it.success && it.data != null) { + val themeList = listOf("전체").union(it.data).toList() + _themeListLiveData.postValue(themeList) + } 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("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.") + } + ) + ) + } + fun loadAll( isFree: Boolean? = null, isPointAvailableOnly: Boolean? = null @@ -44,6 +88,11 @@ class AudioContentAllViewModel( size = size, isFree = isFree, isPointAvailableOnly = isPointAvailableOnly, + theme = if (selectedTheme == "전체") { + null + } else { + selectedTheme + }, token = "Bearer ${SharedPreferenceManager.token}" ) .subscribeOn(Schedulers.io()) @@ -64,4 +113,10 @@ class AudioContentAllViewModel( }) ) } + + fun selectTheme(theme: String, isFree: Boolean, isPointOnly: Boolean) { + reset() + selectedTheme = theme + loadAll(isFree, isPointOnly) + } } diff --git a/app/src/main/res/layout/activity_audio_content_all.xml b/app/src/main/res/layout/activity_audio_content_all.xml index fde77e98..f62c7b7d 100644 --- a/app/src/main/res/layout/activity_audio_content_all.xml +++ b/app/src/main/res/layout/activity_audio_content_all.xml @@ -2,21 +2,58 @@ + android:background="@color/color_131313" + android:orientation="vertical"> + + + + + + + + +