Files
radiola-android/app/src/main/java/com/radiola/ui/charts/ChartsViewModel.kt
nk d0e5f4e8c5 feat(charts): раздел «Чарты» (клиент) + детальная страница трека с графиком
- вкладка «Чарты» в навигации; экран: периоды (День/Неделя/Месяц/Всё),
  ранжированный список треков (ранг, обложка, проигрывания, тренд)
- детальная карточка трека: метрики, график популярности (Canvas), лайк,
  кнопки музыкальных сервисов, кнопка «Текст песни» (ссылка на лицензированный
  Musixmatch — полный текст не встраиваем, авторское право)
- ChartsRepository/LyricsRepository + эндпоинты charts/* в RadiolaApi (DTO)
- превью-данные пока бэкенд не отдаёт charts (помечено TODO)
2026-06-02 23:24:42 +03:00

109 lines
4.0 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.radiola.ui.charts
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.radiola.data.repository.ChartsRepositoryImpl
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()
/** true — данные из превью (бэкенд ещё не готов). */
private val _isPreview = MutableStateFlow(false)
val isPreview: StateFlow<Boolean> = _isPreview.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()
}
fun selectPeriod(newPeriod: ChartPeriod) {
if (_period.value == newPeriod) return
_period.value = newPeriod
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 {
val result = chartsRepository.getCharts(_period.value)
_charts.value = result
// Определяем, это превью или реальные данные.
// TODO: убрать проверку, когда бэкенд отдаёт charts.
// Если список совпадает с образцом — это превью.
_isPreview.value = result == ChartsRepositoryImpl.SAMPLE_CHARTS
} catch (e: Exception) {
Log.e("ChartsViewModel", "Ошибка загрузки чартов", e)
_isPreview.value = true
} finally {
_isLoadingCharts.value = false
}
}
}
}