feat(content): 랭킹 ViewModel을 추가한다

This commit is contained in:
2026-06-24 14:45:10 +09:00
parent f2996f599a
commit f4e46f9d20
3 changed files with 402 additions and 0 deletions

View File

@@ -0,0 +1,97 @@
package kr.co.vividnext.sodalive.v2.main.content
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.R
import kr.co.vividnext.sodalive.base.BaseViewModel
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.common.ToastMessage
import kr.co.vividnext.sodalive.v2.main.content.data.AudioRankingType
import kr.co.vividnext.sodalive.v2.main.content.data.AudioRankingsRepository
import kr.co.vividnext.sodalive.v2.main.content.model.AudioRankingsUiState
import kr.co.vividnext.sodalive.v2.main.content.model.toContentRankingItems
class ContentRankingViewModel(
private val repository: AudioRankingsRepository
) : BaseViewModel() {
private val cachedStates = mutableMapOf<AudioRankingType, AudioRankingsUiState>()
private var latestRequestId = 0L
private val _rankingStateLiveData = MutableLiveData<AudioRankingsUiState>()
val rankingStateLiveData: LiveData<AudioRankingsUiState>
get() = _rankingStateLiveData
private val _toastLiveData = MutableLiveData<ToastMessage?>()
val toastLiveData: LiveData<ToastMessage?>
get() = _toastLiveData
private val _isLoading = MutableLiveData(false)
val isLoading: LiveData<Boolean>
get() = _isLoading
private val _selectedTypeLiveData = MutableLiveData(AudioRankingType.WEEKLY_POPULAR)
val selectedTypeLiveData: LiveData<AudioRankingType>
get() = _selectedTypeLiveData
fun loadRankings(type: AudioRankingType, force: Boolean = false) {
_selectedTypeLiveData.value = type
val requestId = ++latestRequestId
val cachedState = cachedStates[type]
if (!force && cachedState != null) {
_isLoading.value = false
_rankingStateLiveData.value = cachedState
return
}
_isLoading.value = true
_rankingStateLiveData.value = AudioRankingsUiState.Loading
compositeDisposable.add(
repository.getRankings(token = authToken(), type = type)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
if (!isCurrentRequest(requestId, type)) return@subscribe
_isLoading.value = false
val data = it.data
if (it.success && data != null) {
if (data.type != type) return@subscribe
val items = data.toContentRankingItems()
val state = if (items.isEmpty()) {
AudioRankingsUiState.Empty(type)
} else {
AudioRankingsUiState.Content(type, items)
}
cachedStates[type] = state
_rankingStateLiveData.value = state
} else {
showUnknownError(type, it.message)
}
},
{
if (!isCurrentRequest(requestId, type)) return@subscribe
_isLoading.value = false
it.message?.let { message -> Logger.e(message) }
showUnknownError(type, it.message)
}
)
)
}
private fun isCurrentRequest(requestId: Long, type: AudioRankingType): Boolean {
return requestId == latestRequestId && _selectedTypeLiveData.value == type
}
private fun showUnknownError(type: AudioRankingType, message: String?) {
if (_selectedTypeLiveData.value != type) return
_rankingStateLiveData.value = AudioRankingsUiState.Error(type = type, message = message)
_toastLiveData.value = ToastMessage(resId = R.string.common_error_unknown)
}
private fun authToken(): String = "Bearer ${SharedPreferenceManager.token}"
}

View File

@@ -0,0 +1,20 @@
package kr.co.vividnext.sodalive.v2.main.content.model
import kr.co.vividnext.sodalive.v2.main.content.data.AudioRankingType
import kr.co.vividnext.sodalive.v2.widget.contentranking.ContentRankingItem
sealed class AudioRankingsUiState {
data object Loading : AudioRankingsUiState()
data class Content(
val type: AudioRankingType,
val items: List<ContentRankingItem>
) : AudioRankingsUiState()
data class Empty(val type: AudioRankingType) : AudioRankingsUiState()
data class Error(
val type: AudioRankingType,
val message: String?
) : AudioRankingsUiState()
}