Подтягиваем обогащённые данные с бэкенда (Discogs): genre/styles/label/year в чартах и детальной странице. - ChartEntry/TrackStats + DTO: добавлены genre/styles/label/year - RadiolaApi: getCharts(?genre=), новый getGenres() - ChartsViewModel: состояние выбранного жанра + список жанров, перезагрузка - ChartsScreen: ряд чипов-фильтров по жанру (Все + жанры), жанр/стили чипами и «Лейбл · Год» на детальной - убран демо-fallback (SAMPLE_CHARTS) — бэкенд живой Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
120 lines
4.1 KiB
Kotlin
120 lines
4.1 KiB
Kotlin
package com.radiola.ui.charts
|
||
|
||
import android.util.Log
|
||
import androidx.lifecycle.ViewModel
|
||
import androidx.lifecycle.viewModelScope
|
||
import com.radiola.domain.model.ChartEntry
|
||
import com.radiola.domain.model.ChartPeriod
|
||
import com.radiola.domain.model.TrackStats
|
||
import com.radiola.domain.repository.ChartsRepository
|
||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||
import kotlinx.coroutines.flow.MutableStateFlow
|
||
import kotlinx.coroutines.flow.StateFlow
|
||
import kotlinx.coroutines.flow.asStateFlow
|
||
import kotlinx.coroutines.launch
|
||
import javax.inject.Inject
|
||
|
||
@HiltViewModel
|
||
class ChartsViewModel @Inject constructor(
|
||
private val chartsRepository: ChartsRepository
|
||
) : ViewModel() {
|
||
|
||
private val _period = MutableStateFlow(ChartPeriod.WEEK)
|
||
val period: StateFlow<ChartPeriod> = _period.asStateFlow()
|
||
|
||
private val _charts = MutableStateFlow<List<ChartEntry>>(emptyList())
|
||
val charts: StateFlow<List<ChartEntry>> = _charts.asStateFlow()
|
||
|
||
/** Доступные жанры для фильтра (с бэкенда). */
|
||
private val _genres = MutableStateFlow<List<String>>(emptyList())
|
||
val genres: StateFlow<List<String>> = _genres.asStateFlow()
|
||
|
||
/** Выбранный жанр (null — «Все»). */
|
||
private val _selectedGenre = MutableStateFlow<String?>(null)
|
||
val selectedGenre: StateFlow<String?> = _selectedGenre.asStateFlow()
|
||
|
||
private val _isLoadingCharts = MutableStateFlow(false)
|
||
val isLoadingCharts: StateFlow<Boolean> = _isLoadingCharts.asStateFlow()
|
||
|
||
private val _selectedTrackStats = MutableStateFlow<TrackStats?>(null)
|
||
val selectedTrackStats: StateFlow<TrackStats?> = _selectedTrackStats.asStateFlow()
|
||
|
||
private val _isLoadingStats = MutableStateFlow(false)
|
||
val isLoadingStats: StateFlow<Boolean> = _isLoadingStats.asStateFlow()
|
||
|
||
init {
|
||
loadCharts()
|
||
loadGenres()
|
||
}
|
||
|
||
fun selectPeriod(newPeriod: ChartPeriod) {
|
||
if (_period.value == newPeriod) return
|
||
_period.value = newPeriod
|
||
loadCharts()
|
||
}
|
||
|
||
fun selectGenre(genre: String?) {
|
||
if (_selectedGenre.value == genre) return
|
||
_selectedGenre.value = genre
|
||
loadCharts()
|
||
}
|
||
|
||
fun selectTrack(trackId: String) {
|
||
viewModelScope.launch {
|
||
_isLoadingStats.value = true
|
||
_selectedTrackStats.value = null
|
||
try {
|
||
val stats = chartsRepository.getTrackStats(trackId)
|
||
_selectedTrackStats.value = stats
|
||
} catch (e: Exception) {
|
||
Log.e("ChartsViewModel", "Ошибка загрузки статистики трека $trackId", e)
|
||
} finally {
|
||
_isLoadingStats.value = false
|
||
}
|
||
}
|
||
}
|
||
|
||
fun clearSelection() {
|
||
_selectedTrackStats.value = null
|
||
}
|
||
|
||
fun toggleLike(trackId: String) {
|
||
val stats = _selectedTrackStats.value ?: return
|
||
val newLiked = !stats.isLiked
|
||
// Оптимистично обновляем UI
|
||
_selectedTrackStats.value = stats.copy(
|
||
isLiked = newLiked,
|
||
totalLikes = if (newLiked) stats.totalLikes + 1 else stats.totalLikes - 1
|
||
)
|
||
viewModelScope.launch {
|
||
try {
|
||
chartsRepository.setLiked(trackId, newLiked)
|
||
} catch (e: Exception) {
|
||
Log.e("ChartsViewModel", "Ошибка переключения лайка трека $trackId", e)
|
||
// Откатываем при ошибке
|
||
_selectedTrackStats.value = stats
|
||
}
|
||
}
|
||
}
|
||
|
||
private fun loadCharts() {
|
||
viewModelScope.launch {
|
||
_isLoadingCharts.value = true
|
||
try {
|
||
_charts.value = chartsRepository.getCharts(_period.value, _selectedGenre.value)
|
||
} catch (e: Exception) {
|
||
Log.e("ChartsViewModel", "Ошибка загрузки чартов", e)
|
||
_charts.value = emptyList()
|
||
} finally {
|
||
_isLoadingCharts.value = false
|
||
}
|
||
}
|
||
}
|
||
|
||
private fun loadGenres() {
|
||
viewModelScope.launch {
|
||
_genres.value = chartsRepository.getGenres()
|
||
}
|
||
}
|
||
}
|