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
|
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.Icon
|
||||||
import androidx.compose.material3.NavigationBar
|
|
||||||
import androidx.compose.material3.NavigationBarItem
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
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.NavController
|
||||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||||
|
import com.radiola.ui.theme.Motion
|
||||||
|
import com.radiola.ui.theme.RadiolaTheme
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BottomNavBar(navController: NavController) {
|
fun BottomNavBar(navController: NavController) {
|
||||||
|
val colors = RadiolaTheme.colors
|
||||||
val currentRoute = navController.currentBackStackEntryAsState().value?.destination?.route
|
val currentRoute = navController.currentBackStackEntryAsState().value?.destination?.route
|
||||||
NavigationBar {
|
val items = NavDestinations.items.filter { it.showInBottomBar }
|
||||||
NavDestinations.items.filter { it.showInBottomBar }.forEach { destination ->
|
|
||||||
NavigationBarItem(
|
Row(
|
||||||
icon = { Icon(destination.icon, contentDescription = destination.labelRes) },
|
modifier = Modifier
|
||||||
label = { Text(destination.labelRes) },
|
.fillMaxWidth()
|
||||||
selected = currentRoute == destination.route,
|
.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 = {
|
onClick = {
|
||||||
if (currentRoute != destination.route) {
|
if (currentRoute != destination.route) {
|
||||||
navController.navigate(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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
95
app/src/main/java/com/radiola/ui/theme/Brand.kt
Normal file
95
app/src/main/java/com/radiola/ui/theme/Brand.kt
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
package com.radiola.ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Brush
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
|
||||||
|
/** Брендовый градиент иконки. */
|
||||||
|
fun brandGradient(): Brush = Brush.linearGradient(listOf(BrandGradientStart, BrandGradientEnd))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Иконка-марка приложения: градиентный squircle с монограммой «R».
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun AppMark(
|
||||||
|
size: Dp = 76.dp,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val radius = (size.value * 0.29f).dp
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.size(size)
|
||||||
|
.clip(RoundedCornerShape(radius))
|
||||||
|
.background(brandGradient()),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "R",
|
||||||
|
color = BgBase,
|
||||||
|
fontWeight = FontWeight.Black,
|
||||||
|
fontSize = (size.value * 0.62f).sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Текстовый логотип «radiOLA»: «radi» основным цветом, «OLA» акцентом.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun RadiolaWordmark(
|
||||||
|
fontSize: Int = 26,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
Row(modifier = modifier, verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Text(
|
||||||
|
text = "radi",
|
||||||
|
color = TextPrimary,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = fontSize.sp
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "OLA",
|
||||||
|
color = Accent,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = fontSize.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Монохромная марка для нейтральных подложек (уведомления, виджет). */
|
||||||
|
@Composable
|
||||||
|
fun MonoMark(
|
||||||
|
size: Dp = 24.dp,
|
||||||
|
color: Color = TextPrimary,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.size(size)
|
||||||
|
.clip(RoundedCornerShape((size.value * 0.27f).dp))
|
||||||
|
.border(1.5.dp, color, RoundedCornerShape((size.value * 0.27f).dp)),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "R",
|
||||||
|
color = color,
|
||||||
|
fontWeight = FontWeight.Black,
|
||||||
|
fontSize = (size.value * 0.6f).sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,12 +2,23 @@ package com.radiola.ui.theme
|
|||||||
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
val Primary = Color(0xFF6200EE)
|
// Базовая палитра radiOLA (тёмно-зелёная тема)
|
||||||
val PrimaryDark = Color(0xFF3700B3)
|
val BgBase = Color(0xFF0C1410)
|
||||||
val Secondary = Color(0xFF03DAC6)
|
val BgSurface = Color(0xFF16201A)
|
||||||
val Background = Color(0xFF121212)
|
val BgSurface2 = Color(0xFF1E2A23)
|
||||||
val Surface = Color(0xFF1E1E1E)
|
val BgElevated = Color(0xFF243029)
|
||||||
val OnPrimary = Color.White
|
|
||||||
val OnSecondary = Color.Black
|
val Accent = Color(0xFFA8E05F)
|
||||||
val OnBackground = Color.White
|
val AccentDim = Color(0xFF6FA53C)
|
||||||
val OnSurface = Color.White
|
|
||||||
|
val TextPrimary = Color(0xFFFFFFFF)
|
||||||
|
val TextSecondary = Color(0xFF8FA396)
|
||||||
|
val TextMuted = Color(0xFF5C6E63)
|
||||||
|
|
||||||
|
val BorderColor = Color(0xFF2A352E)
|
||||||
|
val LiveRed = Color(0xFFFF5252)
|
||||||
|
val LiveRedSoft = Color(0xFFFF6B6B)
|
||||||
|
|
||||||
|
// Брендовый градиент (иконка приложения, акцентные элементы)
|
||||||
|
val BrandGradientStart = Color(0xFFC2F25B)
|
||||||
|
val BrandGradientEnd = Color(0xFF6FA53C)
|
||||||
|
|||||||
102
app/src/main/java/com/radiola/ui/theme/Motion.kt
Normal file
102
app/src/main/java/com/radiola/ui/theme/Motion.kt
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
package com.radiola.ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.animation.core.Animatable
|
||||||
|
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||||
|
import androidx.compose.animation.core.LinearEasing
|
||||||
|
import androidx.compose.animation.core.RepeatMode
|
||||||
|
import androidx.compose.animation.core.Spring
|
||||||
|
import androidx.compose.animation.core.animateFloat
|
||||||
|
import androidx.compose.animation.core.infiniteRepeatable
|
||||||
|
import androidx.compose.animation.core.rememberInfiniteTransition
|
||||||
|
import androidx.compose.animation.core.spring
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.foundation.Canvas
|
||||||
|
import androidx.compose.foundation.gestures.detectTapGestures
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.interaction.collectIsPressedAsState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.geometry.CornerRadius
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.geometry.Size
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import kotlin.math.abs
|
||||||
|
|
||||||
|
/** Стандартные длительности и кривые движения приложения. */
|
||||||
|
object Motion {
|
||||||
|
const val Fast = 120
|
||||||
|
const val Medium = 220
|
||||||
|
const val Slow = 360
|
||||||
|
|
||||||
|
fun <T> snappy() = spring<T>(
|
||||||
|
dampingRatio = Spring.DampingRatioMediumBouncy,
|
||||||
|
stiffness = Spring.StiffnessMediumLow
|
||||||
|
)
|
||||||
|
|
||||||
|
fun <T> gentle() = tween<T>(durationMillis = Medium, easing = FastOutSlowInEasing)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Лёгкое нажатие: плавное уменьшение масштаба, пока палец на элементе. */
|
||||||
|
@Composable
|
||||||
|
fun Modifier.pressScale(
|
||||||
|
pressedScale: Float = 0.94f,
|
||||||
|
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
|
||||||
|
): Modifier {
|
||||||
|
val pressed by interactionSource.collectIsPressedAsState()
|
||||||
|
val scale by androidx.compose.animation.core.animateFloatAsState(
|
||||||
|
targetValue = if (pressed) pressedScale else 1f,
|
||||||
|
animationSpec = tween(Motion.Fast, easing = FastOutSlowInEasing),
|
||||||
|
label = "pressScale"
|
||||||
|
)
|
||||||
|
return this.graphicsLayer {
|
||||||
|
scaleX = scale
|
||||||
|
scaleY = scale
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Живой эквалайзер для прямого эфира — декоративный, без перемотки.
|
||||||
|
* Полоски плавно «дышат» при воспроизведении.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun LiveEqualizer(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
barCount: Int = 36,
|
||||||
|
color: Color = Accent,
|
||||||
|
playing: Boolean = true
|
||||||
|
) {
|
||||||
|
val transition = rememberInfiniteTransition(label = "eq")
|
||||||
|
val phase by transition.animateFloat(
|
||||||
|
initialValue = 0f,
|
||||||
|
targetValue = (Math.PI * 2).toFloat(),
|
||||||
|
animationSpec = infiniteRepeatable(
|
||||||
|
animation = tween(1400, easing = LinearEasing),
|
||||||
|
repeatMode = RepeatMode.Restart
|
||||||
|
),
|
||||||
|
label = "eqPhase"
|
||||||
|
)
|
||||||
|
Canvas(modifier = modifier) {
|
||||||
|
val gap = 3.dp.toPx()
|
||||||
|
val barWidth = (size.width - gap * (barCount - 1)) / barCount
|
||||||
|
val maxH = size.height
|
||||||
|
for (i in 0 until barCount) {
|
||||||
|
val seed = (i * 0.7f)
|
||||||
|
val wave = if (playing) {
|
||||||
|
0.45f + 0.55f * abs(kotlin.math.sin(phase + seed))
|
||||||
|
} else 0.25f
|
||||||
|
val h = maxH * wave
|
||||||
|
val x = i * (barWidth + gap)
|
||||||
|
val y = (maxH - h) / 2f
|
||||||
|
drawRoundRect(
|
||||||
|
color = color,
|
||||||
|
topLeft = Offset(x, y),
|
||||||
|
size = Size(barWidth, h),
|
||||||
|
cornerRadius = CornerRadius(barWidth / 2f, barWidth / 2f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
app/src/main/java/com/radiola/ui/theme/Shape.kt
Normal file
13
app/src/main/java/com/radiola/ui/theme/Shape.kt
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package com.radiola.ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.Shapes
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
val RadiolaShapes = Shapes(
|
||||||
|
extraSmall = RoundedCornerShape(8.dp),
|
||||||
|
small = RoundedCornerShape(12.dp),
|
||||||
|
medium = RoundedCornerShape(16.dp),
|
||||||
|
large = RoundedCornerShape(20.dp),
|
||||||
|
extraLarge = RoundedCornerShape(28.dp)
|
||||||
|
)
|
||||||
@@ -1,38 +1,69 @@
|
|||||||
package com.radiola.ui.theme
|
package com.radiola.ui.theme
|
||||||
|
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.darkColorScheme
|
import androidx.compose.material3.darkColorScheme
|
||||||
import androidx.compose.material3.lightColorScheme
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.staticCompositionLocalOf
|
||||||
|
import androidx.compose.ui.graphics.Brush
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Расширенные токены, которых нет в Material ColorScheme.
|
||||||
|
* Доступ через MaterialTheme-стиль: `RadiolaTheme.colors`.
|
||||||
|
*/
|
||||||
|
data class RadiolaColors(
|
||||||
|
val bgBase: Color = BgBase,
|
||||||
|
val surface: Color = BgSurface,
|
||||||
|
val surface2: Color = BgSurface2,
|
||||||
|
val elevated: Color = BgElevated,
|
||||||
|
val accent: Color = Accent,
|
||||||
|
val accentDim: Color = AccentDim,
|
||||||
|
val textPrimary: Color = TextPrimary,
|
||||||
|
val textSecondary: Color = TextSecondary,
|
||||||
|
val textMuted: Color = TextMuted,
|
||||||
|
val border: Color = BorderColor,
|
||||||
|
val live: Color = LiveRed,
|
||||||
|
) {
|
||||||
|
val brandGradient: Brush
|
||||||
|
get() = Brush.linearGradient(listOf(BrandGradientStart, BrandGradientEnd))
|
||||||
|
}
|
||||||
|
|
||||||
|
val LocalRadiolaColors = staticCompositionLocalOf { RadiolaColors() }
|
||||||
|
|
||||||
|
object RadiolaTheme {
|
||||||
|
val colors: RadiolaColors
|
||||||
|
@Composable get() = LocalRadiolaColors.current
|
||||||
|
}
|
||||||
|
|
||||||
private val DarkColorScheme = darkColorScheme(
|
private val DarkColorScheme = darkColorScheme(
|
||||||
primary = Primary,
|
primary = Accent,
|
||||||
secondary = Secondary,
|
onPrimary = BgBase,
|
||||||
background = Background,
|
secondary = AccentDim,
|
||||||
surface = Surface,
|
onSecondary = BgBase,
|
||||||
onPrimary = OnPrimary,
|
background = BgBase,
|
||||||
onSecondary = OnSecondary,
|
onBackground = TextPrimary,
|
||||||
onBackground = OnBackground,
|
surface = BgSurface,
|
||||||
onSurface = OnSurface
|
onSurface = TextPrimary,
|
||||||
)
|
surfaceVariant = BgSurface2,
|
||||||
|
onSurfaceVariant = TextSecondary,
|
||||||
private val LightColorScheme = lightColorScheme(
|
outline = BorderColor,
|
||||||
primary = Primary,
|
outlineVariant = BorderColor,
|
||||||
secondary = Secondary,
|
error = LiveRed,
|
||||||
onPrimary = OnPrimary,
|
onError = BgBase,
|
||||||
onSecondary = OnSecondary
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RadiolaTheme(
|
fun RadiolaTheme(
|
||||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
|
||||||
content: @Composable () -> Unit
|
content: @Composable () -> Unit
|
||||||
) {
|
) {
|
||||||
val colorScheme = if (darkTheme) DarkColorScheme else LightColorScheme
|
// Приложение всегда в тёмной фирменной теме.
|
||||||
MaterialTheme(
|
CompositionLocalProvider(LocalRadiolaColors provides RadiolaColors()) {
|
||||||
colorScheme = colorScheme,
|
MaterialTheme(
|
||||||
typography = Typography,
|
colorScheme = DarkColorScheme,
|
||||||
content = content
|
typography = Typography,
|
||||||
)
|
shapes = RadiolaShapes,
|
||||||
|
content = content
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,30 +6,55 @@ import androidx.compose.ui.text.font.FontFamily
|
|||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
|
||||||
|
// Системный гротеск; брендовый wordmark рисуется отдельно (Brand.kt).
|
||||||
|
private val AppFont = FontFamily.Default
|
||||||
|
|
||||||
val Typography = Typography(
|
val Typography = Typography(
|
||||||
headlineLarge = TextStyle(
|
headlineLarge = TextStyle(
|
||||||
fontFamily = FontFamily.Default,
|
fontFamily = AppFont,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
fontSize = 28.sp
|
fontSize = 30.sp,
|
||||||
|
letterSpacing = (-0.5).sp
|
||||||
),
|
),
|
||||||
headlineMedium = TextStyle(
|
headlineMedium = TextStyle(
|
||||||
fontFamily = FontFamily.Default,
|
fontFamily = AppFont,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.Bold,
|
||||||
fontSize = 22.sp
|
fontSize = 26.sp
|
||||||
|
),
|
||||||
|
titleLarge = TextStyle(
|
||||||
|
fontFamily = AppFont,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = 18.sp
|
||||||
),
|
),
|
||||||
titleMedium = TextStyle(
|
titleMedium = TextStyle(
|
||||||
fontFamily = FontFamily.Default,
|
fontFamily = AppFont,
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.SemiBold,
|
||||||
fontSize = 16.sp
|
fontSize = 16.sp
|
||||||
),
|
),
|
||||||
|
bodyLarge = TextStyle(
|
||||||
|
fontFamily = AppFont,
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
fontSize = 15.sp
|
||||||
|
),
|
||||||
bodyMedium = TextStyle(
|
bodyMedium = TextStyle(
|
||||||
fontFamily = FontFamily.Default,
|
fontFamily = AppFont,
|
||||||
fontWeight = FontWeight.Normal,
|
fontWeight = FontWeight.Normal,
|
||||||
fontSize = 14.sp
|
fontSize = 14.sp
|
||||||
),
|
),
|
||||||
|
labelLarge = TextStyle(
|
||||||
|
fontFamily = AppFont,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
fontSize = 13.sp
|
||||||
|
),
|
||||||
labelMedium = TextStyle(
|
labelMedium = TextStyle(
|
||||||
fontFamily = FontFamily.Default,
|
fontFamily = AppFont,
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.Medium,
|
||||||
fontSize = 12.sp
|
fontSize = 12.sp
|
||||||
|
),
|
||||||
|
labelSmall = TextStyle(
|
||||||
|
fontFamily = AppFont,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
fontSize = 10.sp,
|
||||||
|
letterSpacing = 1.sp
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user