feat(app): кнопка «Распознать трек» (Shazam) + история распознанных

- кнопка распознавания в плеере: видна только на музыкальных станциях без
  метаданных эфира (track == null), показывает спиннер и результат через Toast
- распознанный трек отображается в плеере и пишется в ОТДЕЛЬНУЮ историю
  распознанных (не дублируется в историю эфирных треков — гейт по ключу)
- экран Истории: переключатель «Треки эфира | Распознанные», два списка
- Room: таблица recognized_track (миграция 7→8), DAO/репозиторий
- ShazamRepository → POST /shazam/recognize/{stationId}, маппинг 503/400 в текст
- MusicGenres.isMusicStation — клиентский гейт (синхронизирован с бэкендом)
- bump backend submodule (модуль shazam)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
nk
2026-06-07 18:38:17 +03:00
parent 251809df33
commit 69682268f3
16 changed files with 371 additions and 28 deletions

View File

@@ -0,0 +1,18 @@
package com.radiola.domain.model
/**
* Клиентский признак «музыкальная ли станция». На разговорных/юмористических/
* новостных станциях распознавать нечего — кнопку Shazam там не показываем.
* Список синхронизирован с backend (common/station-classification.ts).
*/
object MusicGenres {
private val NON_MUSIC = setOf(
"Станция Кассиопея", "Юмор ФМ", "Рассказы", "Радио Вера",
"Comedy Radio", "ВГТРК", "Старое радио",
)
fun isMusicStation(genre: String?): Boolean {
if (genre.isNullOrBlank()) return true
return genre.trim() !in NON_MUSIC
}
}

View File

@@ -0,0 +1,9 @@
package com.radiola.domain.repository
import com.radiola.domain.model.Track
import kotlinx.coroutines.flow.Flow
interface RecognizedTrackRepository {
fun getHistory(): Flow<List<Track>>
suspend fun addTrack(track: Track)
}

View File

@@ -0,0 +1,13 @@
package com.radiola.domain.repository
import com.radiola.domain.model.Track
sealed interface RecognizeResult {
data class Found(val track: Track) : RecognizeResult
data object NotFound : RecognizeResult
data class Error(val message: String) : RecognizeResult
}
interface ShazamRepository {
suspend fun recognize(stationId: Int, stationName: String): RecognizeResult
}