feat(charts): раздел «Чарты» (клиент) + детальная страница трека с графиком

- вкладка «Чарты» в навигации; экран: периоды (День/Неделя/Месяц/Всё),
  ранжированный список треков (ранг, обложка, проигрывания, тренд)
- детальная карточка трека: метрики, график популярности (Canvas), лайк,
  кнопки музыкальных сервисов, кнопка «Текст песни» (ссылка на лицензированный
  Musixmatch — полный текст не встраиваем, авторское право)
- ChartsRepository/LyricsRepository + эндпоинты charts/* в RadiolaApi (DTO)
- превью-данные пока бэкенд не отдаёт charts (помечено TODO)
This commit is contained in:
nk
2026-06-02 23:24:42 +03:00
parent a4af72a6e6
commit d0e5f4e8c5
15 changed files with 1346 additions and 1 deletions

View File

@@ -0,0 +1,26 @@
package com.radiola.domain.model
/** Период чарта, выбираемый пользователем. */
enum class ChartPeriod(val apiValue: String, val label: String) {
DAY("day", "День"),
WEEK("week", "Неделя"),
MONTH("month", "Месяц"),
ALL("all", "Всё время")
}
/** Направление движения позиции в чарте. */
enum class ChartTrend { UP, DOWN, NEW, SAME }
/** Одна позиция в чарте. */
data class ChartEntry(
val rank: Int,
val trackId: String,
val artist: String,
val song: String,
val coverUrl: String?,
val plays: Int,
val stationsCount: Int,
val likes: Int,
val prevRank: Int?,
val trend: ChartTrend
)

View File

@@ -0,0 +1,34 @@
package com.radiola.domain.model
/** Одна точка на графике популярности. */
data class StatPoint(
/** Метка времени в epoch-миллисекундах. */
val date: Long,
val value: Int
)
/** Проигрывания на конкретной станции. */
data class StationPlays(
val stationId: Int,
val name: String,
val plays: Int
)
/** Полная статистика трека (детальная карточка). */
data class TrackStats(
val trackId: String,
val artist: String,
val song: String,
val album: String?,
val coverUrl: String?,
val releaseDate: String?,
val firstSeen: String?,
val totalPlays: Int,
val totalLikes: Int,
val isLiked: Boolean,
val currentRank: Int?,
val peakRank: Int?,
val stations: List<StationPlays>,
val playsTimeline: List<StatPoint>,
val likesTimeline: List<StatPoint>
)

View File

@@ -0,0 +1,11 @@
package com.radiola.domain.repository
import com.radiola.domain.model.ChartEntry
import com.radiola.domain.model.ChartPeriod
import com.radiola.domain.model.TrackStats
interface ChartsRepository {
suspend fun getCharts(period: ChartPeriod): List<ChartEntry>
suspend fun getTrackStats(trackId: String): TrackStats
suspend fun setLiked(trackId: String, liked: Boolean)
}

View File

@@ -0,0 +1,15 @@
package com.radiola.domain.repository
// Тексты песен — авторское право. Показываем ссылку на лицензированный сервис,
// полный текст не храним/не встраиваем.
// Для сниппета подключить официальный Musixmatch API (с атрибуцией).
interface LyricsRepository {
/** URL поиска на лицензированном сервисе Musixmatch. */
fun providerUrl(artist: String, song: String): String
/**
* Лицензированный сниппет текста.
* TODO: подключить официальный Musixmatch API (с атрибуцией) и вернуть реальный сниппет.
*/
suspend fun snippet(artist: String, song: String): String? = null
}