feat(theme): выбор цветовой темы (8 палитр) в настройках
8 тёмных палитр (Лес/Океан/Закат/Аметист/Неон/Янтарь/Лёд/Роза) в Palettes.kt. RadiolaColors теперь несёт все токены + градиент; RadiolaTheme(palette) строит из неё и RadiolaColors, и Material ColorScheme — всё приложение берёт цвета через RadiolaTheme.colors, поэтому смена палитры перекрашивает мгновенно. Бренд-марка (AppMark/Wordmark) тоже следует теме. Выбор в DataStore (theme_palette, дефолт forest), читается в MainActivity и подаётся в тему. Секция «ТЕМА ОФОРМЛЕНИЯ» в настройках — горизонтальный ряд свотчей с превью (фон+акцент+градиент).
This commit is contained in:
@@ -46,6 +46,9 @@ class MainActivity : ComponentActivity() {
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var tokenDataStore: TokenDataStore
|
lateinit var tokenDataStore: TokenDataStore
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var settingsRepository: com.radiola.domain.repository.SettingsRepository
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
installSplashScreen()
|
installSplashScreen()
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@@ -55,7 +58,9 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
setContent {
|
setContent {
|
||||||
RadiolaTheme {
|
// Выбранная цветовая тема (мгновенно перекрашивает всё приложение).
|
||||||
|
val paletteId by settingsRepository.getThemePalette().collectAsState(initial = "forest")
|
||||||
|
RadiolaTheme(palette = com.radiola.ui.theme.ThemePalette.fromId(paletteId)) {
|
||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||||
var showPlayer by remember { mutableStateOf(false) }
|
var showPlayer by remember { mutableStateOf(false) }
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ class SettingsRepositoryImpl @Inject constructor(
|
|||||||
private val PREFERRED_BITRATE = intPreferencesKey("preferred_bitrate")
|
private val PREFERRED_BITRATE = intPreferencesKey("preferred_bitrate")
|
||||||
private val COUNTRY_CODE = stringPreferencesKey("country_code")
|
private val COUNTRY_CODE = stringPreferencesKey("country_code")
|
||||||
private val VISUALIZER_STYLE = stringPreferencesKey("visualizer_style")
|
private val VISUALIZER_STYLE = stringPreferencesKey("visualizer_style")
|
||||||
|
private val THEME_PALETTE = stringPreferencesKey("theme_palette")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getLastStationId(): Flow<Int?> = dataStore.data.map { it[LAST_STATION_ID] }
|
override fun getLastStationId(): Flow<Int?> = dataStore.data.map { it[LAST_STATION_ID] }
|
||||||
@@ -61,4 +62,7 @@ class SettingsRepositoryImpl @Inject constructor(
|
|||||||
|
|
||||||
override fun getVisualizerStyle(): Flow<String> = dataStore.data.map { it[VISUALIZER_STYLE] ?: "bars_center" }
|
override fun getVisualizerStyle(): Flow<String> = dataStore.data.map { it[VISUALIZER_STYLE] ?: "bars_center" }
|
||||||
override suspend fun setVisualizerStyle(style: String) { dataStore.edit { it[VISUALIZER_STYLE] = style } }
|
override suspend fun setVisualizerStyle(style: String) { dataStore.edit { it[VISUALIZER_STYLE] = style } }
|
||||||
|
|
||||||
|
override fun getThemePalette(): Flow<String> = dataStore.data.map { it[THEME_PALETTE] ?: "forest" }
|
||||||
|
override suspend fun setThemePalette(id: String) { dataStore.edit { it[THEME_PALETTE] = id } }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,4 +23,7 @@ interface SettingsRepository {
|
|||||||
// Стиль визуализатора звука в плеере (ключ VisualizerStyle).
|
// Стиль визуализатора звука в плеере (ключ VisualizerStyle).
|
||||||
fun getVisualizerStyle(): Flow<String>
|
fun getVisualizerStyle(): Flow<String>
|
||||||
suspend fun setVisualizerStyle(style: String)
|
suspend fun setVisualizerStyle(style: String)
|
||||||
|
// Цветовая тема приложения (id ThemePalette, напр. "forest"). По умолчанию "forest".
|
||||||
|
fun getThemePalette(): Flow<String>
|
||||||
|
suspend fun setThemePalette(id: String)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ import androidx.compose.animation.core.tween
|
|||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.horizontalScroll
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
@@ -32,6 +34,7 @@ import com.radiola.domain.model.DeeplinkService
|
|||||||
import com.radiola.domain.model.StationTestStatus
|
import com.radiola.domain.model.StationTestStatus
|
||||||
import com.radiola.ui.theme.Motion
|
import com.radiola.ui.theme.Motion
|
||||||
import com.radiola.ui.theme.RadiolaTheme
|
import com.radiola.ui.theme.RadiolaTheme
|
||||||
|
import com.radiola.ui.theme.ThemePalette
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -45,6 +48,7 @@ fun SettingsScreen(
|
|||||||
val enabledServices by viewModel.enabledServices.collectAsState()
|
val enabledServices by viewModel.enabledServices.collectAsState()
|
||||||
val equalizerPreset by viewModel.equalizerPreset.collectAsState()
|
val equalizerPreset by viewModel.equalizerPreset.collectAsState()
|
||||||
val visualizerStyle by viewModel.visualizerStyle.collectAsState()
|
val visualizerStyle by viewModel.visualizerStyle.collectAsState()
|
||||||
|
val themePalette by viewModel.themePalette.collectAsState()
|
||||||
val isRecordingEnabled by viewModel.isRecordingEnabled.collectAsState()
|
val isRecordingEnabled by viewModel.isRecordingEnabled.collectAsState()
|
||||||
val preferredBitrate by viewModel.preferredBitrate.collectAsState()
|
val preferredBitrate by viewModel.preferredBitrate.collectAsState()
|
||||||
val isTesting by viewModel.isTesting.collectAsState()
|
val isTesting by viewModel.isTesting.collectAsState()
|
||||||
@@ -76,6 +80,26 @@ fun SettingsScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Тема оформления ---
|
||||||
|
item {
|
||||||
|
SectionLabel("ТЕМА ОФОРМЛЕНИЯ")
|
||||||
|
Spacer(Modifier.height(8.dp))
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.horizontalScroll(rememberScrollState()),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
ThemePalette.entries.forEach { palette ->
|
||||||
|
ThemeSwatch(
|
||||||
|
palette = palette,
|
||||||
|
selected = themePalette == palette.id,
|
||||||
|
onClick = { viewModel.setThemePalette(palette.id) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- Профиль ---
|
// --- Профиль ---
|
||||||
item {
|
item {
|
||||||
SectionLabel("ПРОФИЛЬ")
|
SectionLabel("ПРОФИЛЬ")
|
||||||
@@ -594,6 +618,61 @@ fun SettingsScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Превью цветовой темы: квадрат с фоном палитры, акцентным кружком и брендовым
|
||||||
|
* градиентом снизу. Выбранная — с акцентной рамкой и жирной подписью.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
private fun ThemeSwatch(
|
||||||
|
palette: ThemePalette,
|
||||||
|
selected: Boolean,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
val p = palette.colors
|
||||||
|
val outer = RadiolaTheme.colors
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.width(72.dp)
|
||||||
|
.clip(RoundedCornerShape(18.dp))
|
||||||
|
.clickable(onClick = onClick),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(6.dp)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(64.dp)
|
||||||
|
.clip(RoundedCornerShape(18.dp))
|
||||||
|
.background(p.bgBase)
|
||||||
|
.border(
|
||||||
|
width = if (selected) 2.dp else 1.dp,
|
||||||
|
color = if (selected) outer.accent else outer.border,
|
||||||
|
shape = RoundedCornerShape(18.dp)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.Center)
|
||||||
|
.size(26.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(p.accent)
|
||||||
|
)
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.BottomCenter)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(10.dp)
|
||||||
|
.background(p.brandGradient)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text = palette.title,
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
color = if (selected) outer.accent else outer.textSecondary,
|
||||||
|
fontWeight = if (selected) FontWeight.SemiBold else FontWeight.Normal
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Подпись секции: заглавные буквы, textMuted, labelSmall. */
|
/** Подпись секции: заглавные буквы, textMuted, labelSmall. */
|
||||||
@Composable
|
@Composable
|
||||||
private fun SectionLabel(text: String) {
|
private fun SectionLabel(text: String) {
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ class SettingsViewModel @Inject constructor(
|
|||||||
val visualizerStyle: StateFlow<String> = settingsRepository.getVisualizerStyle()
|
val visualizerStyle: StateFlow<String> = settingsRepository.getVisualizerStyle()
|
||||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), "bars_center")
|
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), "bars_center")
|
||||||
|
|
||||||
|
val themePalette: StateFlow<String> = settingsRepository.getThemePalette()
|
||||||
|
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), "forest")
|
||||||
|
|
||||||
val isRecordingEnabled: StateFlow<Boolean> = settingsRepository.isRecordingEnabled()
|
val isRecordingEnabled: StateFlow<Boolean> = settingsRepository.isRecordingEnabled()
|
||||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), false)
|
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), false)
|
||||||
|
|
||||||
@@ -84,6 +87,10 @@ class SettingsViewModel @Inject constructor(
|
|||||||
viewModelScope.launch { settingsRepository.setVisualizerStyle(style) }
|
viewModelScope.launch { settingsRepository.setVisualizerStyle(style) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setThemePalette(id: String) {
|
||||||
|
viewModelScope.launch { settingsRepository.setThemePalette(id) }
|
||||||
|
}
|
||||||
|
|
||||||
fun setRecordingEnabled(enabled: Boolean) {
|
fun setRecordingEnabled(enabled: Boolean) {
|
||||||
viewModelScope.launch { settingsRepository.setRecordingEnabled(enabled) }
|
viewModelScope.launch { settingsRepository.setRecordingEnabled(enabled) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,16 +31,17 @@ fun AppMark(
|
|||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val radius = (size.value * 0.29f).dp
|
val radius = (size.value * 0.29f).dp
|
||||||
|
val colors = RadiolaTheme.colors
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.size(size)
|
.size(size)
|
||||||
.clip(RoundedCornerShape(radius))
|
.clip(RoundedCornerShape(radius))
|
||||||
.background(brandGradient()),
|
.background(colors.brandGradient),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "R",
|
text = "R",
|
||||||
color = BgBase,
|
color = colors.bgBase,
|
||||||
fontWeight = FontWeight.Black,
|
fontWeight = FontWeight.Black,
|
||||||
fontSize = (size.value * 0.62f).sp
|
fontSize = (size.value * 0.62f).sp
|
||||||
)
|
)
|
||||||
@@ -55,16 +56,17 @@ fun RadiolaWordmark(
|
|||||||
fontSize: Int = 26,
|
fontSize: Int = 26,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
|
val colors = RadiolaTheme.colors
|
||||||
Row(modifier = modifier, verticalAlignment = Alignment.CenterVertically) {
|
Row(modifier = modifier, verticalAlignment = Alignment.CenterVertically) {
|
||||||
Text(
|
Text(
|
||||||
text = "radi",
|
text = "radi",
|
||||||
color = TextPrimary,
|
color = colors.textPrimary,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
fontSize = fontSize.sp
|
fontSize = fontSize.sp
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = "OLA",
|
text = "OLA",
|
||||||
color = Accent,
|
color = colors.accent,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
fontSize = fontSize.sp
|
fontSize = fontSize.sp
|
||||||
)
|
)
|
||||||
|
|||||||
119
app/src/main/java/com/radiola/ui/theme/Palettes.kt
Normal file
119
app/src/main/java/com/radiola/ui/theme/Palettes.kt
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
package com.radiola.ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Цветовые темы приложения. radiOLA всегда тёмная — палитры различаются оттенком
|
||||||
|
* фона, акцентом и брендовым градиентом. Выбор хранится по [id] в настройках
|
||||||
|
* (`SettingsRepository.getThemePalette`), применяется в `RadiolaTheme`.
|
||||||
|
*
|
||||||
|
* Все цвета берутся компонентами через `RadiolaTheme.colors` / `MaterialTheme.colorScheme`,
|
||||||
|
* поэтому смена палитры перекрашивает приложение мгновенно.
|
||||||
|
*/
|
||||||
|
enum class ThemePalette(
|
||||||
|
val id: String,
|
||||||
|
val title: String,
|
||||||
|
val colors: RadiolaColors,
|
||||||
|
) {
|
||||||
|
// Фирменная зелёная (по умолчанию) — берёт значения из Color.kt.
|
||||||
|
FOREST(
|
||||||
|
"forest", "Лес",
|
||||||
|
RadiolaColors(
|
||||||
|
bgBase = BgBase, surface = BgSurface, surface2 = BgSurface2, elevated = BgElevated,
|
||||||
|
accent = Accent, accentDim = AccentDim,
|
||||||
|
textPrimary = TextPrimary, textSecondary = TextSecondary, textMuted = TextMuted,
|
||||||
|
border = BorderColor, live = LiveRed,
|
||||||
|
gradientStart = BrandGradientStart, gradientEnd = BrandGradientEnd,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Глубокий сине-бирюзовый, акцент циан.
|
||||||
|
OCEAN(
|
||||||
|
"ocean", "Океан",
|
||||||
|
RadiolaColors(
|
||||||
|
bgBase = Color(0xFF0A0F1A), surface = Color(0xFF121A2A), surface2 = Color(0xFF1A2438), elevated = Color(0xFF222F47),
|
||||||
|
accent = Color(0xFF4FD6E0), accentDim = Color(0xFF2E8E9A),
|
||||||
|
textPrimary = Color(0xFFFFFFFF), textSecondary = Color(0xFF8F9DB3), textMuted = Color(0xFF5C6A82),
|
||||||
|
border = Color(0xFF26324A), live = Color(0xFFFF5C7A),
|
||||||
|
gradientStart = Color(0xFF5BE1F2), gradientEnd = Color(0xFF3A7BD5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Тёплый тёмный, коралл/оранжевый.
|
||||||
|
SUNSET(
|
||||||
|
"sunset", "Закат",
|
||||||
|
RadiolaColors(
|
||||||
|
bgBase = Color(0xFF1A0F0C), surface = Color(0xFF261712), surface2 = Color(0xFF33201A), elevated = Color(0xFF422A22),
|
||||||
|
accent = Color(0xFFFF8A5B), accentDim = Color(0xFFC55E3A),
|
||||||
|
textPrimary = Color(0xFFFFFFFF), textSecondary = Color(0xFFB39A8F), textMuted = Color(0xFF82675C),
|
||||||
|
border = Color(0xFF4A322A), live = Color(0xFFFF4D6D),
|
||||||
|
gradientStart = Color(0xFFFFB36B), gradientEnd = Color(0xFFFF6B5B),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Тёмно-фиолетовый, акцент сирень.
|
||||||
|
AMETHYST(
|
||||||
|
"amethyst", "Аметист",
|
||||||
|
RadiolaColors(
|
||||||
|
bgBase = Color(0xFF120E1A), surface = Color(0xFF1C1528), surface2 = Color(0xFF271D38), elevated = Color(0xFF332747),
|
||||||
|
accent = Color(0xFFB388FF), accentDim = Color(0xFF7E5BC5),
|
||||||
|
textPrimary = Color(0xFFFFFFFF), textSecondary = Color(0xFFA095B3), textMuted = Color(0xFF6E5C82),
|
||||||
|
border = Color(0xFF372A4A), live = Color(0xFFFF5C9D),
|
||||||
|
gradientStart = Color(0xFFC9A6FF), gradientEnd = Color(0xFF8B5BD5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Почти чёрный, неоновый розово-малиновый (киберпанк).
|
||||||
|
NEON(
|
||||||
|
"neon", "Неон",
|
||||||
|
RadiolaColors(
|
||||||
|
bgBase = Color(0xFF0D0A12), surface = Color(0xFF16111E), surface2 = Color(0xFF1F1729), elevated = Color(0xFF2A1F38),
|
||||||
|
accent = Color(0xFFFF4D9D), accentDim = Color(0xFFC53A75),
|
||||||
|
textPrimary = Color(0xFFFFFFFF), textSecondary = Color(0xFFB095A6), textMuted = Color(0xFF7C5C6E),
|
||||||
|
border = Color(0xFF3A2A33), live = Color(0xFFFF3D5C),
|
||||||
|
gradientStart = Color(0xFFFF5BD0), gradientEnd = Color(0xFFFF4D7A),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Тёплый уголь, золотой акцент.
|
||||||
|
AMBER(
|
||||||
|
"amber", "Янтарь",
|
||||||
|
RadiolaColors(
|
||||||
|
bgBase = Color(0xFF14110A), surface = Color(0xFF201A10), surface2 = Color(0xFF2C2418), elevated = Color(0xFF392F20),
|
||||||
|
accent = Color(0xFFFFC247), accentDim = Color(0xFFC5902E),
|
||||||
|
textPrimary = Color(0xFFFFFFFF), textSecondary = Color(0xFFB3A88F), textMuted = Color(0xFF82765C),
|
||||||
|
border = Color(0xFF4A3F2A), live = Color(0xFFFF5C52),
|
||||||
|
gradientStart = Color(0xFFFFD66B), gradientEnd = Color(0xFFFF9F45),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Холодный графит, ледяной голубой.
|
||||||
|
ICE(
|
||||||
|
"ice", "Лёд",
|
||||||
|
RadiolaColors(
|
||||||
|
bgBase = Color(0xFF0C1014), surface = Color(0xFF161C22), surface2 = Color(0xFF202830), elevated = Color(0xFF2C3640),
|
||||||
|
accent = Color(0xFF7FB3FF), accentDim = Color(0xFF4F7CC5),
|
||||||
|
textPrimary = Color(0xFFFFFFFF), textSecondary = Color(0xFF95A3B3), textMuted = Color(0xFF5C6A7C),
|
||||||
|
border = Color(0xFF2A3540), live = Color(0xFFFF6B6B),
|
||||||
|
gradientStart = Color(0xFFA6D0FF), gradientEnd = Color(0xFF6B9FFF),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Тёмная мальва, розовый акцент.
|
||||||
|
ROSE(
|
||||||
|
"rose", "Роза",
|
||||||
|
RadiolaColors(
|
||||||
|
bgBase = Color(0xFF160E12), surface = Color(0xFF22151B), surface2 = Color(0xFF2E1E26), elevated = Color(0xFF3C2A33),
|
||||||
|
accent = Color(0xFFFF7EA8), accentDim = Color(0xFFC5547A),
|
||||||
|
textPrimary = Color(0xFFFFFFFF), textSecondary = Color(0xFFB395A0), textMuted = Color(0xFF825C6A),
|
||||||
|
border = Color(0xFF4A2A38), live = Color(0xFFFF4D6D),
|
||||||
|
gradientStart = Color(0xFFFFA6C2), gradientEnd = Color(0xFFFF6B9F),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/** Палитра по сохранённому id (фолбэк — «Лес»). */
|
||||||
|
fun fromId(id: String?): ThemePalette = entries.firstOrNull { it.id == id } ?: FOREST
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,54 +13,60 @@ import androidx.compose.ui.graphics.Color
|
|||||||
* Доступ через MaterialTheme-стиль: `RadiolaTheme.colors`.
|
* Доступ через MaterialTheme-стиль: `RadiolaTheme.colors`.
|
||||||
*/
|
*/
|
||||||
data class RadiolaColors(
|
data class RadiolaColors(
|
||||||
val bgBase: Color = BgBase,
|
val bgBase: Color,
|
||||||
val surface: Color = BgSurface,
|
val surface: Color,
|
||||||
val surface2: Color = BgSurface2,
|
val surface2: Color,
|
||||||
val elevated: Color = BgElevated,
|
val elevated: Color,
|
||||||
val accent: Color = Accent,
|
val accent: Color,
|
||||||
val accentDim: Color = AccentDim,
|
val accentDim: Color,
|
||||||
val textPrimary: Color = TextPrimary,
|
val textPrimary: Color,
|
||||||
val textSecondary: Color = TextSecondary,
|
val textSecondary: Color,
|
||||||
val textMuted: Color = TextMuted,
|
val textMuted: Color,
|
||||||
val border: Color = BorderColor,
|
val border: Color,
|
||||||
val live: Color = LiveRed,
|
val live: Color,
|
||||||
|
val gradientStart: Color,
|
||||||
|
val gradientEnd: Color,
|
||||||
) {
|
) {
|
||||||
val brandGradient: Brush
|
val brandGradient: Brush
|
||||||
get() = Brush.linearGradient(listOf(BrandGradientStart, BrandGradientEnd))
|
get() = Brush.linearGradient(listOf(gradientStart, gradientEnd))
|
||||||
}
|
}
|
||||||
|
|
||||||
val LocalRadiolaColors = staticCompositionLocalOf { RadiolaColors() }
|
// По умолчанию — фирменная палитра «Лес».
|
||||||
|
val LocalRadiolaColors = staticCompositionLocalOf { ThemePalette.FOREST.colors }
|
||||||
|
|
||||||
object RadiolaTheme {
|
object RadiolaTheme {
|
||||||
val colors: RadiolaColors
|
val colors: RadiolaColors
|
||||||
@Composable get() = LocalRadiolaColors.current
|
@Composable get() = LocalRadiolaColors.current
|
||||||
}
|
}
|
||||||
|
|
||||||
private val DarkColorScheme = darkColorScheme(
|
// Material ColorScheme из наших токенов — чтобы Material-компоненты тоже следовали палитре.
|
||||||
primary = Accent,
|
private fun schemeOf(c: RadiolaColors) = darkColorScheme(
|
||||||
onPrimary = BgBase,
|
primary = c.accent,
|
||||||
secondary = AccentDim,
|
onPrimary = c.bgBase,
|
||||||
onSecondary = BgBase,
|
secondary = c.accentDim,
|
||||||
background = BgBase,
|
onSecondary = c.bgBase,
|
||||||
onBackground = TextPrimary,
|
background = c.bgBase,
|
||||||
surface = BgSurface,
|
onBackground = c.textPrimary,
|
||||||
onSurface = TextPrimary,
|
surface = c.surface,
|
||||||
surfaceVariant = BgSurface2,
|
onSurface = c.textPrimary,
|
||||||
onSurfaceVariant = TextSecondary,
|
surfaceVariant = c.surface2,
|
||||||
outline = BorderColor,
|
onSurfaceVariant = c.textSecondary,
|
||||||
outlineVariant = BorderColor,
|
outline = c.border,
|
||||||
error = LiveRed,
|
outlineVariant = c.border,
|
||||||
onError = BgBase,
|
error = c.live,
|
||||||
|
onError = c.bgBase,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RadiolaTheme(
|
fun RadiolaTheme(
|
||||||
|
palette: ThemePalette = ThemePalette.FOREST,
|
||||||
content: @Composable () -> Unit
|
content: @Composable () -> Unit
|
||||||
) {
|
) {
|
||||||
// Приложение всегда в тёмной фирменной теме.
|
// Приложение всегда тёмное; палитра выбирается пользователем.
|
||||||
CompositionLocalProvider(LocalRadiolaColors provides RadiolaColors()) {
|
val colors = palette.colors
|
||||||
|
CompositionLocalProvider(LocalRadiolaColors provides colors) {
|
||||||
MaterialTheme(
|
MaterialTheme(
|
||||||
colorScheme = DarkColorScheme,
|
colorScheme = schemeOf(colors),
|
||||||
typography = Typography,
|
typography = Typography,
|
||||||
shapes = RadiolaShapes,
|
shapes = RadiolaShapes,
|
||||||
content = content
|
content = content
|
||||||
|
|||||||
Reference in New Issue
Block a user