polish(ui): плавные обложки, бегущая строка длинных названий, тактильный отклик

- Coil crossfade для всех обложек (Images.crossfadeModel) — без «моргания» при загрузке
- basicMarquee для длинных названий трека (плеер и мини-плеер) вместо обрезки
- haptic feedback на play/pause и добавление в избранное (плеер, мини-плеер, карточка)
This commit is contained in:
nk
2026-06-02 22:33:33 +03:00
parent 310d6c3177
commit a4af72a6e6
5 changed files with 58 additions and 13 deletions

View File

@@ -1,6 +1,8 @@
package com.radiola.ui.player
import android.util.Log
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.basicMarquee
import androidx.compose.animation.Crossfade
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.tween
@@ -20,7 +22,9 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
@@ -47,6 +51,7 @@ import com.radiola.ui.theme.Motion
import com.radiola.ui.theme.RadiolaTheme
import com.radiola.ui.theme.pressScale
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun PlayerBottomSheet(
station: Station?,
@@ -65,6 +70,7 @@ fun PlayerBottomSheet(
val context = LocalContext.current
val enabledServices by viewModel.enabledServices.collectAsState()
val colors = RadiolaTheme.colors
val haptics = LocalHapticFeedback.current
Column(
modifier = modifier
@@ -95,7 +101,7 @@ fun PlayerBottomSheet(
val coverModel = track?.coverUrl ?: station?.coverUrl
if (!coverModel.isNullOrBlank()) {
AsyncImage(
model = coverModel,
model = com.radiola.ui.components.crossfadeModel(coverModel),
contentDescription = station?.name,
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop
@@ -123,8 +129,8 @@ fun PlayerBottomSheet(
style = MaterialTheme.typography.headlineLarge,
color = colors.textPrimary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
textAlign = androidx.compose.ui.text.style.TextAlign.Center
textAlign = androidx.compose.ui.text.style.TextAlign.Center,
modifier = Modifier.basicMarquee()
)
Spacer(Modifier.height(4.dp))
Text(
@@ -161,7 +167,13 @@ fun PlayerBottomSheet(
label = "heartTint"
)
PlayerIconBtn(size = 44.dp) {
IconButton(onClick = onToggleFavorite, modifier = Modifier.size(44.dp)) {
IconButton(
onClick = {
haptics.performHapticFeedback(HapticFeedbackType.LongPress)
onToggleFavorite()
},
modifier = Modifier.size(44.dp)
) {
Icon(Lucide.Heart, "Избранное", tint = heartTint, modifier = Modifier.size(22.dp))
}
}
@@ -181,7 +193,10 @@ fun PlayerBottomSheet(
.clip(CircleShape)
.background(colors.accent)
.pressScale(interactionSource = playInteraction)
.clickable(interactionSource = playInteraction, indication = null, onClick = onPlayPause),
.clickable(interactionSource = playInteraction, indication = null) {
haptics.performHapticFeedback(HapticFeedbackType.LongPress)
onPlayPause()
},
contentAlignment = Alignment.Center
) {
Crossfade(