feat(audio-content-all): theme 추가

This commit is contained in:
2025-11-20 02:32:42 +09:00
parent 5fa4c42119
commit a15b478ac6
7 changed files with 195 additions and 15 deletions

View File

@@ -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<ApiResponse<List<AudioContentMainItem>>>
@@ -71,6 +73,15 @@ interface AudioContentApi {
@Header("Authorization") authHeader: String
): Single<ApiResponse<List<GetAudioContentThemeResponse>>>
@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<ApiResponse<List<String>>>
@GET("/audio-content/theme/{id}/content")
fun getAudioContentByTheme(
@Path("id") id: Long,

View File

@@ -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
)
}

View File

@@ -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

View File

@@ -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<ActivityAudioContentAllBinding>(
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<ActivityAudioContentAllBinding>(
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<ActivityAudioContentAllBinding>(
}
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<ActivityAudioContentAllBinding>(
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

View File

@@ -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<List<AudioContentMainItem>>()
val itemsLiveData: LiveData<List<AudioContentMainItem>> get() = _itemsLiveData
private var _themeListLiveData = MutableLiveData<List<String>>()
val themeListLiveData: LiveData<List<String>>
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)
}
}

View File

@@ -2,21 +2,58 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/color_131313">
android:background="@color/color_131313"
android:orientation="vertical">
<include
android:id="@+id/toolbar"
layout="@layout/detail_toolbar" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_theme"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:clipToPadding="false"
android:paddingHorizontal="16dp" />
<LinearLayout
android:id="@+id/ll_sort"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end"
android:orientation="horizontal"
android:paddingHorizontal="20dp"
android:paddingVertical="12dp">
<TextView
android:id="@+id/tv_sort_newest"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/pretendard_medium"
android:gravity="center"
android:text="최신순"
android:textColor="@color/color_88e2e2e2"
android:textSize="16sp" />
<TextView
android:id="@+id/tv_sort_popularity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:fontFamily="@font/pretendard_medium"
android:gravity="center"
android:text="인기순"
android:textColor="@color/color_88e2e2e2"
android:textSize="16sp" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_content"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:clipToPadding="false"
android:paddingHorizontal="13.3dp"
android:paddingTop="8dp"
android:paddingBottom="16dp" />
<LinearLayout

View File

@@ -10,11 +10,12 @@
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@drawable/bg_round_corner_16_7_transparent_777777"
android:fontFamily="@font/pretendard_medium"
android:gravity="center"
android:paddingHorizontal="13.3dp"
android:paddingVertical="9.3dp"
android:paddingHorizontal="12dp"
android:paddingVertical="8dp"
android:textColor="@color/color_777777"
android:textSize="14.7sp"
android:textSize="14sp"
tools:text="자작곡" />
</FrameLayout>