feat(stations): скрывать украинские станции (ROKS, Kiss FM) для РФ

Radio ROKS и Kiss FM (TavR Media, хосты radioroks.ua / kissfm.ua) недоступны
с российских IP без VPN. Теперь для пользователей из РФ они полностью скрыты
— и сами станции (везде, где используется список), и их чипы-категории.

Страна определяется по IP (api.country.is → ipapi.co; при VPN вернёт страну
выходного узла, тогда станции доступны и НЕ скрываются), с фолбэком на страну
SIM/сети/локали устройства, если IP-сервис недоступен (в РФ часто заблокирован).
Код страны кэшируется (DataStore). Фильтр в GetStationsUseCase (combine со
страной) + чипы в StationsViewModel. id 741 «Радио РОКС» (stream.roks.com) —
российская, под правило не попадает.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
nk
2026-06-04 14:46:42 +03:00
parent 4697e27eb4
commit 8d2c53c441
8 changed files with 162 additions and 4 deletions

View File

@@ -0,0 +1,26 @@
package com.radiola.domain.geo
import com.radiola.domain.model.Station
/**
* Гео-ограничения станций. Украинские потоки (TavR Media: Radio ROKS, Kiss FM)
* не отдаются на российские IP (geoblock) — для пользователей из РФ их полностью
* скрываем (и сами станции, и их чипы-категории). При VPN (не-RU IP) — показываем,
* т.к. потоки тогда доступны.
*/
object GeoBlock {
// Хосты украинских станций. id 741 «Радио РОКС» (stream.roks.com) — российская,
// под это правило НЕ попадает.
private val UA_HOSTS = listOf("radioroks.ua", "kissfm.ua")
private val BLOCKED_COUNTRIES = setOf("RU")
fun isUaStation(station: Station): Boolean =
UA_HOSTS.any { station.streamUrl.contains(it, ignoreCase = true) }
/** Скрыта ли станция для пользователя из данной страны. */
fun isHidden(station: Station, countryCode: String?): Boolean =
countryCode in BLOCKED_COUNTRIES && isUaStation(station)
/** Нужно ли вообще скрывать украинские станции в этой стране. */
fun shouldHideUa(countryCode: String?): Boolean = countryCode in BLOCKED_COUNTRIES
}

View File

@@ -0,0 +1,12 @@
package com.radiola.domain.repository
import kotlinx.coroutines.flow.Flow
/** Регион пользователя (по IP) — для гео-фильтрации станций. */
interface RegionRepository {
/** Код страны пользователя (напр. "RU"), null пока не определён. */
fun countryCode(): Flow<String?>
/** Обновить код страны по IP (кэшируется). */
suspend fun refresh()
}

View File

@@ -17,4 +17,7 @@ interface SettingsRepository {
// Предпочитаемый битрейт (kbps). 0 = авто (брать качество по умолчанию станции).
fun getPreferredBitrate(): Flow<Int>
suspend fun setPreferredBitrate(bitrate: Int)
// Код страны пользователя (по IP), напр. "RU". null — не определён.
fun getCountryCode(): Flow<String?>
suspend fun setCountryCode(code: String)
}

View File

@@ -1,12 +1,23 @@
package com.radiola.domain.usecase
import com.radiola.domain.geo.GeoBlock
import com.radiola.domain.model.Station
import com.radiola.domain.repository.RegionRepository
import com.radiola.domain.repository.StationRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import javax.inject.Inject
class GetStationsUseCase @Inject constructor(
private val stationRepository: StationRepository
private val stationRepository: StationRepository,
private val regionRepository: RegionRepository
) {
operator fun invoke(): Flow<List<Station>> = stationRepository.getStations()
// Гео-фильтр: для пользователей из РФ убираем недоступные украинские станции
// (Radio ROKS, Kiss FM) из всех мест, где используется список станций.
operator fun invoke(): Flow<List<Station>> = combine(
stationRepository.getStations(),
regionRepository.countryCode()
) { stations, country ->
stations.filterNot { GeoBlock.isHidden(it, country) }
}
}