feat(filters): чипы растворяются под кнопкой-категорией (fade left edge)
Кнопка-категории теперь ПОВЕРХ чипов (Box-оверлей), чипы идут во всю ширину с отступом слева под кнопку. У левого края — затухание прозрачности (Modifier. fadingStartEdge: graphicsLayer Offscreen + horizontalGradient BlendMode.DstIn), так чипы при прокрутке влево красиво уплывают под кнопку и растворяются, а не обрезаются. FilterChips/GenreSelector получили параметр contentPadding. Экраны Радио и Чарты.
This commit is contained in:
@@ -46,6 +46,7 @@ import com.radiola.domain.model.StatPoint
|
|||||||
import com.radiola.domain.model.TrackStats
|
import com.radiola.domain.model.TrackStats
|
||||||
import com.radiola.ui.components.CategoryPicker
|
import com.radiola.ui.components.CategoryPicker
|
||||||
import com.radiola.ui.components.EmptyState
|
import com.radiola.ui.components.EmptyState
|
||||||
|
import com.radiola.ui.components.fadingStartEdge
|
||||||
import com.radiola.ui.components.PopularityChart
|
import com.radiola.ui.components.PopularityChart
|
||||||
import com.radiola.ui.components.crossfadeModel
|
import com.radiola.ui.components.crossfadeModel
|
||||||
import com.radiola.ui.components.serviceLogoRes
|
import com.radiola.ui.components.serviceLogoRes
|
||||||
@@ -102,19 +103,23 @@ fun ChartsScreen(
|
|||||||
// Фильтр по жанру (если бэкенд уже накопил жанры)
|
// Фильтр по жанру (если бэкенд уже накопил жанры)
|
||||||
if (genres.isNotEmpty()) {
|
if (genres.isNotEmpty()) {
|
||||||
Spacer(Modifier.height(10.dp))
|
Spacer(Modifier.height(10.dp))
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Box(modifier = Modifier.fillMaxWidth().height(44.dp)) {
|
||||||
|
GenreSelector(
|
||||||
|
genres = genres,
|
||||||
|
selected = selectedGenre,
|
||||||
|
onSelect = viewModel::selectGenre,
|
||||||
|
contentPadding = PaddingValues(start = 70.dp, end = 20.dp),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.align(Alignment.Center)
|
||||||
|
.fadingStartEdge(62.dp)
|
||||||
|
)
|
||||||
CategoryPicker(
|
CategoryPicker(
|
||||||
title = "Стиль музыки",
|
title = "Стиль музыки",
|
||||||
items = genres,
|
items = genres,
|
||||||
selected = selectedGenre,
|
selected = selectedGenre,
|
||||||
onSelect = viewModel::selectGenre,
|
onSelect = viewModel::selectGenre,
|
||||||
modifier = Modifier.padding(start = 20.dp)
|
modifier = Modifier.align(Alignment.CenterStart).padding(start = 20.dp)
|
||||||
)
|
|
||||||
GenreSelector(
|
|
||||||
genres = genres,
|
|
||||||
selected = selectedGenre,
|
|
||||||
onSelect = viewModel::selectGenre,
|
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -225,12 +230,13 @@ private fun GenreSelector(
|
|||||||
genres: List<String>,
|
genres: List<String>,
|
||||||
selected: String?,
|
selected: String?,
|
||||||
onSelect: (String?) -> Unit,
|
onSelect: (String?) -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier,
|
||||||
|
contentPadding: PaddingValues = PaddingValues(horizontal = 20.dp)
|
||||||
) {
|
) {
|
||||||
LazyRow(
|
LazyRow(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
horizontalArrangement = Arrangement.spacedBy(9.dp),
|
horizontalArrangement = Arrangement.spacedBy(9.dp),
|
||||||
contentPadding = PaddingValues(horizontal = 20.dp)
|
contentPadding = contentPadding
|
||||||
) {
|
) {
|
||||||
item {
|
item {
|
||||||
PeriodChip(
|
PeriodChip(
|
||||||
|
|||||||
@@ -20,18 +20,45 @@ import androidx.compose.animation.animateColorAsState
|
|||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.draw.drawWithContent
|
||||||
|
import androidx.compose.ui.graphics.BlendMode
|
||||||
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.CompositingStrategy
|
||||||
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.radiola.ui.theme.Motion
|
import com.radiola.ui.theme.Motion
|
||||||
import com.radiola.ui.theme.RadiolaTheme
|
import com.radiola.ui.theme.RadiolaTheme
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Плавное затухание содержимого у ЛЕВОГО края (прозрачность) на ширину [width].
|
||||||
|
* Чипы, заезжая под кнопку-«категории», красиво растворяются, а не обрезаются.
|
||||||
|
*/
|
||||||
|
fun Modifier.fadingStartEdge(width: Dp): Modifier = this
|
||||||
|
.graphicsLayer { compositingStrategy = CompositingStrategy.Offscreen }
|
||||||
|
.drawWithContent {
|
||||||
|
drawContent()
|
||||||
|
val w = width.toPx().coerceAtMost(size.width)
|
||||||
|
drawRect(
|
||||||
|
brush = Brush.horizontalGradient(
|
||||||
|
colorStops = arrayOf(
|
||||||
|
0f to Color.Transparent,
|
||||||
|
(w / size.width) to Color.Black
|
||||||
|
)
|
||||||
|
),
|
||||||
|
blendMode = BlendMode.DstIn
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun FilterChips(
|
fun FilterChips(
|
||||||
tags: List<String>,
|
tags: List<String>,
|
||||||
selectedTag: String?,
|
selectedTag: String?,
|
||||||
onTagSelected: (String?) -> Unit,
|
onTagSelected: (String?) -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier,
|
||||||
|
contentPadding: PaddingValues = PaddingValues(horizontal = 16.dp)
|
||||||
) {
|
) {
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
// Доводим выбранный чип в зону видимости (важно при свайп-переключении).
|
// Доводим выбранный чип в зону видимости (важно при свайп-переключении).
|
||||||
@@ -43,7 +70,7 @@ fun FilterChips(
|
|||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
state = listState,
|
state = listState,
|
||||||
horizontalArrangement = Arrangement.spacedBy(9.dp),
|
horizontalArrangement = Arrangement.spacedBy(9.dp),
|
||||||
contentPadding = PaddingValues(horizontal = 16.dp)
|
contentPadding = contentPadding
|
||||||
) {
|
) {
|
||||||
item {
|
item {
|
||||||
Chip(label = "Все", selected = selectedTag == null) { onTagSelected(null) }
|
Chip(label = "Все", selected = selectedTag == null) { onTagSelected(null) }
|
||||||
|
|||||||
@@ -178,19 +178,26 @@ fun StationsScreen(
|
|||||||
)
|
)
|
||||||
.padding(top = 2.dp, bottom = 12.dp)
|
.padding(top = 2.dp, bottom = 12.dp)
|
||||||
) {
|
) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Box(modifier = Modifier.fillMaxWidth().height(44.dp)) {
|
||||||
|
// Чипы во всю ширину, но с отступом слева под кнопку; у левого
|
||||||
|
// края — затухание прозрачности (чипы «уплывают» под кнопку).
|
||||||
|
FilterChips(
|
||||||
|
tags = tags,
|
||||||
|
selectedTag = selectedTag,
|
||||||
|
onTagSelected = viewModel::onTagSelected,
|
||||||
|
contentPadding = PaddingValues(start = 66.dp, end = 16.dp),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.align(Alignment.Center)
|
||||||
|
.fadingStartEdge(60.dp)
|
||||||
|
)
|
||||||
|
// Кнопка-категории — поверх чипов, слева.
|
||||||
CategoryPicker(
|
CategoryPicker(
|
||||||
title = "Категории",
|
title = "Категории",
|
||||||
items = tags,
|
items = tags,
|
||||||
selected = selectedTag,
|
selected = selectedTag,
|
||||||
onSelect = viewModel::onTagSelected,
|
onSelect = viewModel::onTagSelected,
|
||||||
modifier = Modifier.padding(start = 16.dp)
|
modifier = Modifier.align(Alignment.CenterStart).padding(start = 16.dp)
|
||||||
)
|
|
||||||
FilterChips(
|
|
||||||
tags = tags,
|
|
||||||
selectedTag = selectedTag,
|
|
||||||
onTagSelected = viewModel::onTagSelected,
|
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user