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

@@ -2,9 +2,11 @@ package com.radiola.data.remote
import com.radiola.data.remote.dto.AuthResponseDto
import com.radiola.data.remote.dto.BackendStationDto
import com.radiola.data.remote.dto.ChartsResponseDto
import com.radiola.data.remote.dto.HistoryResponseDto
import com.radiola.data.remote.dto.MagicLinkRequestDto
import com.radiola.data.remote.dto.MagicLinkVerifyDto
import com.radiola.data.remote.dto.TrackStatsDto
import com.radiola.data.remote.dto.UserSettingsDto
import kotlinx.serialization.json.JsonObject
import retrofit2.http.Body
@@ -13,6 +15,7 @@ import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.PATCH
import retrofit2.http.Path
import retrofit2.http.Query
interface RadiolaApi {
@@ -45,4 +48,21 @@ interface RadiolaApi {
@POST("users/me/history/{stationId}")
suspend fun addHistory(@Path("stationId") stationId: String): JsonObject
// --- Чарты ---
@GET("charts/tracks")
suspend fun getCharts(
@Query("period") period: String,
@Query("limit") limit: Int = 100
): ChartsResponseDto
@GET("charts/tracks/{trackId}")
suspend fun getTrackStats(@Path("trackId") trackId: String): TrackStatsDto
@POST("charts/tracks/{trackId}/like")
suspend fun likeTrack(@Path("trackId") trackId: String): JsonObject
@DELETE("charts/tracks/{trackId}/like")
suspend fun unlikeTrack(@Path("trackId") trackId: String): JsonObject
}

View File

@@ -0,0 +1,60 @@
package com.radiola.data.remote.dto
import kotlinx.serialization.Serializable
/** DTO ответа чартов — список позиций. */
@Serializable
data class ChartsResponseDto(
val items: List<ChartEntryDto> = emptyList()
)
/** Одна позиция в чарте. */
@Serializable
data class ChartEntryDto(
val rank: Int,
val trackId: String,
val artist: String,
val song: String,
val coverUrl: String? = null,
val plays: Int = 0,
val stationsCount: Int = 0,
val likes: Int = 0,
val prevRank: Int? = null,
/** Направление: up | down | new | same */
val trend: String = "same"
)
/** Подробная статистика трека. */
@Serializable
data class TrackStatsDto(
val trackId: String,
val artist: String,
val song: String,
val album: String? = null,
val coverUrl: String? = null,
val releaseDate: String? = null,
val firstSeen: String? = null,
val totalPlays: Int = 0,
val totalLikes: Int = 0,
val isLiked: Boolean = false,
val currentRank: Int? = null,
val peakRank: Int? = null,
val stations: List<StationPlaysDto> = emptyList(),
val playsTimeline: List<PointDto> = emptyList(),
val likesTimeline: List<PointDto> = emptyList()
)
/** Проигрывания на конкретной станции. */
@Serializable
data class StationPlaysDto(
val stationId: Int,
val name: String,
val plays: Int
)
/** Одна точка тайм-лайна. */
@Serializable
data class PointDto(
val date: String,
val value: Int
)