feat(nav+fav): порядок меню, анимация иконки при выборе, индикатор на избранном
- Порядок нижнего меню: Радио · Избранное · История · Чарты · Запись · Настройки. - Иконка вкладки при выборе делает упругий scale-«поп» (spring MediumBouncy) — в нижнем баре и боковом рейле. - На экране «Избранное» играющая станция теперь подсвечивается так же, как на главной: вращающееся свечение под обложкой + индикатор-эквалайзер в углу (FavoritesViewModel отдаёт playingStationId/isPlaying из PlayerController, FavoritesScreen передаёт isCurrent/isPlaying в StationCard).
This commit is contained in:
@@ -35,6 +35,8 @@ fun FavoritesScreen(
|
||||
val favorites by viewModel.favorites.collectAsState()
|
||||
val favoriteIds by viewModel.favoriteIds.collectAsState()
|
||||
val nowPlaying by viewModel.nowPlaying.collectAsState()
|
||||
val playingStationId by viewModel.playingStationId.collectAsState()
|
||||
val isPlaying by viewModel.isPlaying.collectAsState()
|
||||
val colors = RadiolaTheme.colors
|
||||
|
||||
Column(
|
||||
@@ -95,6 +97,8 @@ fun FavoritesScreen(
|
||||
onClick = { onStationClick(station) },
|
||||
onFavoriteClick = { viewModel.toggleFavorite(station) },
|
||||
nowTrack = nowPlaying[station.id],
|
||||
isCurrent = station.id == playingStationId,
|
||||
isPlaying = isPlaying,
|
||||
modifier = Modifier.animateItemPlacement()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.radiola.domain.repository.NowPlayingRepository
|
||||
import com.radiola.domain.usecase.ToggleFavoriteUseCase
|
||||
import com.radiola.domain.usecase.auth.PushFavoriteUseCase
|
||||
import com.radiola.domain.usecase.auth.SyncFavoritesUseCase
|
||||
import com.radiola.service.PlayerController
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
@@ -24,9 +25,14 @@ class FavoritesViewModel @Inject constructor(
|
||||
private val toggleFavoriteUseCase: ToggleFavoriteUseCase,
|
||||
private val pushFavoriteUseCase: PushFavoriteUseCase,
|
||||
private val syncFavoritesUseCase: SyncFavoritesUseCase,
|
||||
private val nowPlayingRepository: NowPlayingRepository
|
||||
private val nowPlayingRepository: NowPlayingRepository,
|
||||
private val playerController: PlayerController
|
||||
) : ViewModel() {
|
||||
|
||||
// Активная (играющая) станция — для подсветки карточки, как на экране всех станций.
|
||||
val playingStationId: StateFlow<Int?> = playerController.currentStationId
|
||||
val isPlaying: StateFlow<Boolean> = playerController.isPlaying
|
||||
|
||||
val favorites: StateFlow<List<Station>> = favoritesRepository.getFavorites()
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
|
||||
|
||||
|
||||
@@ -2,6 +2,9 @@ package com.radiola.ui.navigation
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.Spring
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.expandHorizontally
|
||||
import androidx.compose.animation.fadeIn
|
||||
@@ -28,11 +31,13 @@ import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||
@@ -43,9 +48,9 @@ import com.radiola.ui.theme.RadiolaTheme
|
||||
// при холодном старте может содержать null (порядок инициализации Kotlin).
|
||||
private val navItems = listOf(
|
||||
NavDestinations.Stations,
|
||||
NavDestinations.Charts,
|
||||
NavDestinations.Favorites,
|
||||
NavDestinations.History,
|
||||
NavDestinations.Charts,
|
||||
NavDestinations.Recordings,
|
||||
NavDestinations.Settings
|
||||
)
|
||||
@@ -144,6 +149,13 @@ private fun VerticalPillTab(
|
||||
animationSpec = tween(Motion.Medium),
|
||||
label = "railTabFg"
|
||||
)
|
||||
val pop = remember { Animatable(1f) }
|
||||
LaunchedEffect(selected) {
|
||||
if (selected) {
|
||||
pop.snapTo(0.5f)
|
||||
pop.animateTo(1f, spring(dampingRatio = Spring.DampingRatioMediumBouncy, stiffness = 620f))
|
||||
}
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.size(52.dp)
|
||||
@@ -161,7 +173,7 @@ private fun VerticalPillTab(
|
||||
imageVector = icon,
|
||||
contentDescription = label,
|
||||
tint = content,
|
||||
modifier = Modifier.size(22.dp)
|
||||
modifier = Modifier.size(22.dp).scale(pop.value)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -185,6 +197,14 @@ private fun PillTab(
|
||||
animationSpec = tween(Motion.Medium),
|
||||
label = "tabFg"
|
||||
)
|
||||
// Упругий «поп» иконки при выборе вкладки — маленькая приятная деталь.
|
||||
val pop = remember { Animatable(1f) }
|
||||
LaunchedEffect(selected) {
|
||||
if (selected) {
|
||||
pop.snapTo(0.5f)
|
||||
pop.animateTo(1f, spring(dampingRatio = Spring.DampingRatioMediumBouncy, stiffness = 620f))
|
||||
}
|
||||
}
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
@@ -204,7 +224,7 @@ private fun PillTab(
|
||||
imageVector = icon,
|
||||
contentDescription = label,
|
||||
tint = content,
|
||||
modifier = Modifier.height(22.dp).width(22.dp)
|
||||
modifier = Modifier.height(22.dp).width(22.dp).scale(pop.value)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user