fix(ui): иконочный таб-бар, заголовок станций, ровные кнопки плеера, рабочая ссылка на текст

- таб-бар только иконки (6 разделов не помещались с подписями)
- «Откройте радио» -> «Выберите радиостанцию»
- кнопки плеера (лайк/prev/next/запись) единого размера 24/48, ряд SpaceBetween
  (кнопка записи больше не обрезается и не выбивается размером)
- текст песни: Musixmatch резал соединение -> веб-поиск трека (открывается)
This commit is contained in:
nk
2026-06-03 11:15:29 +03:00
parent fc9b23f62c
commit a50a108f63
4 changed files with 18 additions and 28 deletions

View File

@@ -12,8 +12,11 @@ import javax.inject.Singleton
class LyricsRepositoryImpl @Inject constructor() : LyricsRepository { class LyricsRepositoryImpl @Inject constructor() : LyricsRepository {
override fun providerUrl(artist: String, song: String): String { override fun providerUrl(artist: String, song: String): String {
val query = URLEncoder.encode("$artist $song", "UTF-8") // Musixmatch блокирует прямые переходы (connection reset). Открываем
return "https://www.musixmatch.com/search/$query" // веб-поиск по треку — пользователь сам выбирает сервис с текстом.
// Сам текст не встраиваем и не храним (авторское право).
val query = URLEncoder.encode("$artist $song текст песни", "UTF-8")
return "https://yandex.ru/search/?text=$query"
} }
// TODO: подключить официальный Musixmatch API (с атрибуцией) и вернуть реальный сниппет. // TODO: подключить официальный Musixmatch API (с атрибуцией) и вернуть реальный сниппет.

View File

@@ -66,7 +66,7 @@ fun BottomNavBar(navController: NavController) {
label = destination.labelRes, label = destination.labelRes,
icon = destination.icon, icon = destination.icon,
selected = selected, selected = selected,
modifier = Modifier.weight(if (selected) 1.9f else 1f), modifier = Modifier.weight(1f),
onClick = { onClick = {
if (currentRoute != destination.route) { if (currentRoute != destination.route) {
navController.navigate(destination.route) { navController.navigate(destination.route) {
@@ -114,26 +114,12 @@ private fun PillTab(
horizontalArrangement = Arrangement.Center, horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
// Только иконки — подписи не помещались для 6 разделов.
Icon( Icon(
imageVector = icon, imageVector = icon,
contentDescription = label, contentDescription = label,
tint = content, tint = content,
modifier = Modifier.height(18.dp).width(18.dp) modifier = Modifier.height(22.dp).width(22.dp)
) )
AnimatedVisibility(
visible = selected,
enter = fadeIn(tween(Motion.Medium)) + expandHorizontally(tween(Motion.Medium)),
exit = fadeOut(tween(Motion.Fast)) + shrinkHorizontally(tween(Motion.Fast))
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Spacer(Modifier.width(8.dp))
Text(
text = label.uppercase(),
color = content,
style = androidx.compose.material3.MaterialTheme.typography.labelSmall,
maxLines = 1
)
}
}
} }
} }

View File

@@ -157,7 +157,8 @@ fun PlayerBottomSheet(
// Управление воспроизведением // Управление воспроизведением
Row( Row(
horizontalArrangement = Arrangement.spacedBy(24.dp), modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
// Кнопка избранного // Кнопка избранного
@@ -166,15 +167,15 @@ fun PlayerBottomSheet(
animationSpec = tween(Motion.Medium), animationSpec = tween(Motion.Medium),
label = "heartTint" label = "heartTint"
) )
PlayerIconBtn(size = 44.dp) { PlayerIconBtn(size = 48.dp) {
IconButton( IconButton(
onClick = { onClick = {
haptics.performHapticFeedback(HapticFeedbackType.LongPress) haptics.performHapticFeedback(HapticFeedbackType.LongPress)
onToggleFavorite() onToggleFavorite()
}, },
modifier = Modifier.size(44.dp) modifier = Modifier.size(48.dp)
) { ) {
Icon(Lucide.Heart, "Избранное", tint = heartTint, modifier = Modifier.size(22.dp)) Icon(Lucide.Heart, "Избранное", tint = heartTint, modifier = Modifier.size(24.dp))
} }
} }
@@ -226,8 +227,8 @@ fun PlayerBottomSheet(
animationSpec = tween(Motion.Medium), animationSpec = tween(Motion.Medium),
label = "recordTint" label = "recordTint"
) )
PlayerIconBtn(size = 44.dp) { PlayerIconBtn(size = 48.dp) {
IconButton(onClick = onToggleRecording, modifier = Modifier.size(44.dp)) { IconButton(onClick = onToggleRecording, modifier = Modifier.size(48.dp)) {
Crossfade( Crossfade(
targetState = isRecording, targetState = isRecording,
animationSpec = tween(Motion.Fast), animationSpec = tween(Motion.Fast),
@@ -237,7 +238,7 @@ fun PlayerBottomSheet(
imageVector = if (recording) Lucide.MicOff else Lucide.Mic, imageVector = if (recording) Lucide.MicOff else Lucide.Mic,
contentDescription = if (recording) "Остановить запись" else "Запись", contentDescription = if (recording) "Остановить запись" else "Запись",
tint = recordTint, tint = recordTint,
modifier = Modifier.size(20.dp) modifier = Modifier.size(24.dp)
) )
} }
} }

View File

@@ -40,8 +40,8 @@ fun StationsScreen(
// Двухцветный заголовок экрана // Двухцветный заголовок экрана
Text( Text(
text = buildAnnotatedString { text = buildAnnotatedString {
withStyle(SpanStyle(color = colors.textPrimary)) { append("Откройте ") } withStyle(SpanStyle(color = colors.textPrimary)) { append("Выберите ") }
withStyle(SpanStyle(color = colors.accent)) { append("радио") } withStyle(SpanStyle(color = colors.accent)) { append("радиостанцию") }
}, },
style = MaterialTheme.typography.headlineLarge, style = MaterialTheme.typography.headlineLarge,
modifier = Modifier.padding(start = 20.dp, end = 20.dp, top = 20.dp, bottom = 16.dp) modifier = Modifier.padding(start = 20.dp, end = 20.dp, top = 20.dp, bottom = 16.dp)