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:
nk
2026-06-06 19:11:33 +03:00
parent d9acc0efb4
commit ed926e0a9d
8 changed files with 261 additions and 36 deletions

View File

@@ -5,7 +5,9 @@ import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
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.ui.theme.Motion
import com.radiola.ui.theme.RadiolaTheme
import com.radiola.ui.theme.ThemePalette
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -45,6 +48,7 @@ fun SettingsScreen(
val enabledServices by viewModel.enabledServices.collectAsState()
val equalizerPreset by viewModel.equalizerPreset.collectAsState()
val visualizerStyle by viewModel.visualizerStyle.collectAsState()
val themePalette by viewModel.themePalette.collectAsState()
val isRecordingEnabled by viewModel.isRecordingEnabled.collectAsState()
val preferredBitrate by viewModel.preferredBitrate.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 {
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. */
@Composable
private fun SectionLabel(text: String) {