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:
@@ -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) {
|
||||
|
||||
@@ -35,6 +35,9 @@ class SettingsViewModel @Inject constructor(
|
||||
val visualizerStyle: StateFlow<String> = settingsRepository.getVisualizerStyle()
|
||||
.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()
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), false)
|
||||
|
||||
@@ -84,6 +87,10 @@ class SettingsViewModel @Inject constructor(
|
||||
viewModelScope.launch { settingsRepository.setVisualizerStyle(style) }
|
||||
}
|
||||
|
||||
fun setThemePalette(id: String) {
|
||||
viewModelScope.launch { settingsRepository.setThemePalette(id) }
|
||||
}
|
||||
|
||||
fun setRecordingEnabled(enabled: Boolean) {
|
||||
viewModelScope.launch { settingsRepository.setRecordingEnabled(enabled) }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user