feat(ui): дизайн-система radiOLA — палитра, тема, типографика, бренд, motion, pill таб-бар
- цветовые токены тёмно-зелёной темы + RadiolaColors (CompositionLocal) - darkColorScheme + всегда тёмная тема, фирменные shapes - типографика с весами/размерами под макет - Brand: AppMark (градиентный R), RadiolaWordmark, MonoMark - Motion: спеки движения, pressScale, живой эквалайзер - pill-таб-бар с анимированной активной вкладкой
This commit is contained in:
@@ -1,22 +1,63 @@
|
||||
package com.radiola.ui.navigation
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.expandHorizontally
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.shrinkHorizontally
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.NavigationBar
|
||||
import androidx.compose.material3.NavigationBarItem
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
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.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||
import com.radiola.ui.theme.Motion
|
||||
import com.radiola.ui.theme.RadiolaTheme
|
||||
|
||||
@Composable
|
||||
fun BottomNavBar(navController: NavController) {
|
||||
val colors = RadiolaTheme.colors
|
||||
val currentRoute = navController.currentBackStackEntryAsState().value?.destination?.route
|
||||
NavigationBar {
|
||||
NavDestinations.items.filter { it.showInBottomBar }.forEach { destination ->
|
||||
NavigationBarItem(
|
||||
icon = { Icon(destination.icon, contentDescription = destination.labelRes) },
|
||||
label = { Text(destination.labelRes) },
|
||||
selected = currentRoute == destination.route,
|
||||
val items = NavDestinations.items.filter { it.showInBottomBar }
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 16.dp, end = 16.dp, top = 8.dp, bottom = 16.dp)
|
||||
.clip(RoundedCornerShape(36.dp))
|
||||
.background(colors.surface2)
|
||||
.border(1.dp, colors.border, RoundedCornerShape(36.dp))
|
||||
.height(62.dp)
|
||||
.padding(6.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
items.forEach { destination ->
|
||||
val selected = currentRoute == destination.route
|
||||
PillTab(
|
||||
label = destination.labelRes,
|
||||
icon = destination.icon,
|
||||
selected = selected,
|
||||
modifier = Modifier.weight(if (selected) 1.9f else 1f),
|
||||
onClick = {
|
||||
if (currentRoute != destination.route) {
|
||||
navController.navigate(destination.route) {
|
||||
@@ -30,3 +71,60 @@ fun BottomNavBar(navController: NavController) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PillTab(
|
||||
label: String,
|
||||
icon: androidx.compose.ui.graphics.vector.ImageVector,
|
||||
selected: Boolean,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val colors = RadiolaTheme.colors
|
||||
val bg by animateColorAsState(
|
||||
targetValue = if (selected) colors.accent else androidx.compose.ui.graphics.Color.Transparent,
|
||||
animationSpec = tween(Motion.Medium),
|
||||
label = "tabBg"
|
||||
)
|
||||
val content by animateColorAsState(
|
||||
targetValue = if (selected) colors.bgBase else colors.textSecondary,
|
||||
animationSpec = tween(Motion.Medium),
|
||||
label = "tabFg"
|
||||
)
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.height(50.dp)
|
||||
.clip(RoundedCornerShape(26.dp))
|
||||
.background(bg)
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = null,
|
||||
onClick = onClick
|
||||
),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = label,
|
||||
tint = content,
|
||||
modifier = Modifier.height(18.dp).width(18.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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user