Better color schemes

This commit is contained in:
Sad Ellie 2023-04-23 17:23:28 +03:00
parent 68ab0c2cba
commit efc1c9a841
7 changed files with 224 additions and 13 deletions

View File

@ -64,7 +64,8 @@ internal fun UnittoApp() {
themingMode = userPrefs.value.themingMode ?: return,
dynamicThemeEnabled = userPrefs.value.enableDynamicTheme,
amoledThemeEnabled = userPrefs.value.enableAmoledTheme,
customColor = userPrefs.value.customColor
customColor = userPrefs.value.customColor,
monetMode = userPrefs.value.monetMode
)
val navController = rememberNavController()
val sysUiController = rememberSystemUiController()
@ -100,7 +101,7 @@ internal fun UnittoApp() {
Themmo(
themmoController = themmoController,
typography = AppTypography,
animationSpec = tween(150)
animationSpec = tween(450)
) {
val statusBarColor = when (currentRoute) {
// Match text field container color

View File

@ -1324,6 +1324,7 @@
<string name="enable_dynamic_colors_support">Use colors from your wallpaper</string>
<string name="color_scheme">Color scheme</string>
<string name="selected_color">Selected color</string>
<string name="monet_mode">Selected style</string>
<!--MISC.-->
<string name="loading_label">Loading…</string>

View File

@ -35,6 +35,7 @@ import com.sadellie.unitto.data.model.AbstractUnit
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.data.model.UnitsListSorting
import com.sadellie.unitto.data.units.MyUnitIDS
import io.github.sadellie.themmo.MonetMode
import io.github.sadellie.themmo.ThemingMode
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
@ -68,6 +69,7 @@ data class UserPreferences(
val enableDynamicTheme: Boolean = false,
val enableAmoledTheme: Boolean = false,
val customColor: Color = Color.Unspecified,
val monetMode: MonetMode = MonetMode.TONAL_SPOT,
val digitsPrecision: Int = 3,
val separator: Int = Separator.SPACES,
val outputFormat: Int = OutputFormat.PLAIN,
@ -95,6 +97,7 @@ class UserPreferencesRepository @Inject constructor(private val dataStore: DataS
val ENABLE_DYNAMIC_THEME = booleanPreferencesKey("ENABLE_DYNAMIC_THEME_PREF_KEY")
val ENABLE_AMOLED_THEME = booleanPreferencesKey("ENABLE_AMOLED_THEME_PREF_KEY")
val CUSTOM_COLOR = longPreferencesKey("CUSTOM_COLOR_PREF_KEY")
val MONET_MODE = stringPreferencesKey("MONET_MODE_PREF_KEY")
val DIGITS_PRECISION = intPreferencesKey("DIGITS_PRECISION_PREF_KEY")
val SEPARATOR = intPreferencesKey("SEPARATOR_PREF_KEY")
val OUTPUT_FORMAT = intPreferencesKey("OUTPUT_FORMAT_PREF_KEY")
@ -119,12 +122,13 @@ class UserPreferencesRepository @Inject constructor(private val dataStore: DataS
}
}
.map { preferences ->
val themingMode: ThemingMode =
preferences[PrefsKeys.THEMING_MODE]?.let { ThemingMode.valueOf(it) }
?: ThemingMode.AUTO
val themingMode: ThemingMode = preferences[PrefsKeys.THEMING_MODE]?.let { ThemingMode.valueOf(it) }
?: ThemingMode.AUTO
val enableDynamicTheme: Boolean = preferences[PrefsKeys.ENABLE_DYNAMIC_THEME] ?: false
val enableAmoledTheme: Boolean = preferences[PrefsKeys.ENABLE_AMOLED_THEME] ?: false
val customColor: Color = preferences[PrefsKeys.CUSTOM_COLOR]?.let { Color(it.toULong()) } ?: Color.Unspecified
val monetMode: MonetMode = preferences[PrefsKeys.MONET_MODE]?.let { MonetMode.valueOf(it) }
?: MonetMode.TONAL_SPOT
val digitsPrecision: Int = preferences[PrefsKeys.DIGITS_PRECISION] ?: 3
val separator: Int = preferences[PrefsKeys.SEPARATOR] ?: Separator.SPACES
val outputFormat: Int = preferences[PrefsKeys.OUTPUT_FORMAT] ?: OutputFormat.PLAIN
@ -156,6 +160,7 @@ class UserPreferencesRepository @Inject constructor(private val dataStore: DataS
enableDynamicTheme = enableDynamicTheme,
enableAmoledTheme = enableAmoledTheme,
customColor = customColor,
monetMode = monetMode,
digitsPrecision = digitsPrecision,
separator = separator,
outputFormat = outputFormat,
@ -263,6 +268,17 @@ class UserPreferencesRepository @Inject constructor(private val dataStore: DataS
}
}
/**
* Update [MonetMode]. Saves value as a string.
*
* @param monetMode [MonetMode] to save.
*/
suspend fun updateMonetMode(monetMode: MonetMode) {
dataStore.edit { preferences ->
preferences[PrefsKeys.MONET_MODE] = monetMode.name
}
}
/**
* Update preference on starting screen route.
*

View File

@ -28,6 +28,7 @@ import com.sadellie.unitto.data.unitgroups.UnitGroupsRepository
import com.sadellie.unitto.data.userprefs.UserPreferences
import com.sadellie.unitto.data.userprefs.UserPreferencesRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import io.github.sadellie.themmo.MonetMode
import io.github.sadellie.themmo.ThemingMode
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.first
@ -86,6 +87,15 @@ class SettingsViewModel @Inject constructor(
}
}
/**
* @see UserPreferencesRepository.updateMonetMode
*/
fun updateMonetMode(monetMode: MonetMode) {
viewModelScope.launch {
userPrefsRepository.updateMonetMode(monetMode)
}
}
/**
* @see UserPreferencesRepository.updateDigitsPrecision
*/

View File

@ -50,18 +50,23 @@ import com.sadellie.unitto.core.ui.common.SegmentedButtonRow
import com.sadellie.unitto.core.ui.common.UnittoListItem
import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar
import com.sadellie.unitto.feature.settings.components.ColorSelector
import com.sadellie.unitto.feature.settings.components.MonetModeSelector
import io.github.sadellie.themmo.MonetMode
import io.github.sadellie.themmo.ThemingMode
import io.github.sadellie.themmo.ThemmoController
private val colorSchemes: List<Color> by lazy {
listOf(
Color(0xFF8A3B31),
Color(0xFFA5744A),
Color(0xFF6C8B48),
Color(0xFF27B089),
Color(0xFF5C7BAA),
Color(0xFF544F80),
Color(0xFF9A2A9E),
Color(0xFFF6A1BC),
Color(0xFFFDA387),
Color(0xFFEAAF60),
Color(0xFFC2BD64),
Color(0xFF94C78A),
Color(0xFF73C9B5),
Color(0xFF72C6DA),
Color(0xFF8FBEF2),
Color(0xFFB6B3F6),
Color(0xFFDCA8E4),
)
}
@ -93,6 +98,11 @@ internal fun ThemesRoute(
themmoController.setCustomColor(it)
viewModel.updateCustomColor(it)
},
monetMode = themmoController.currentMonetMode,
onMonetModeChange = {
themmoController.setMonetMode(it)
viewModel.updateMonetMode(it)
}
)
}
@ -107,6 +117,8 @@ private fun ThemesScreen(
onAmoledThemeChange: (Boolean) -> Unit,
selectedColor: Color,
onColorChange: (Color) -> Unit,
monetMode: MonetMode,
onMonetModeChange: (MonetMode) -> Unit
) {
val themingModes by remember {
mutableStateOf(
@ -212,6 +224,30 @@ private fun ThemesScreen(
)
}
}
item {
AnimatedVisibility(
visible = (!isDynamicThemeEnabled) and (selectedColor != Color.Unspecified),
enter = expandVertically() + fadeIn(),
exit = shrinkVertically() + fadeOut(),
) {
ListItem(
headlineContent = { Text(stringResource(R.string.monet_mode)) },
supportingContent = {
MonetModeSelector(
modifier = Modifier.padding(top = 12.dp),
selected = monetMode,
onItemClick = onMonetModeChange,
monetModes = remember { MonetMode.values().toList() },
customColor = selectedColor,
themingMode = currentThemingMode,
amoledThemeEnabled = isAmoledThemeEnabled,
)
},
modifier = Modifier.padding(start = 40.dp)
)
}
}
}
}
}
@ -229,5 +265,7 @@ private fun Preview() {
onAmoledThemeChange = {},
selectedColor = Color.Unspecified,
onColorChange = {},
monetMode = MonetMode.TONAL_SPOT,
onMonetModeChange = {}
)
}

View File

@ -0,0 +1,145 @@
/*
* Unitto is a unit converter for Android
* Copyright (c) 2023 Elshan Agaev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.feature.settings.components
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.material3.surfaceColorAtElevation
import androidx.compose.runtime.Composable
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.graphics.Color
import androidx.compose.ui.unit.dp
import io.github.sadellie.themmo.MonetMode
import io.github.sadellie.themmo.ThemingMode
import io.github.sadellie.themmo.Themmo
import io.github.sadellie.themmo.ThemmoController
@Composable
internal fun MonetModeSelector(
modifier: Modifier = Modifier,
selected: MonetMode,
onItemClick: (MonetMode) -> Unit,
monetModes: List<MonetMode>,
customColor: Color,
themingMode: ThemingMode,
amoledThemeEnabled: Boolean,
) {
LazyRow(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
items(monetModes) { monetMode ->
Themmo(
themmoController = remember(customColor) {
ThemmoController(
lightColorScheme = lightColorScheme(),
darkColorScheme = darkColorScheme(),
themingMode = themingMode,
dynamicThemeEnabled = false,
amoledThemeEnabled = false,
customColor = customColor,
monetMode = monetMode
)
}
) {
MonetModeCheckbox(
selected = monetMode == selected,
onClick = { onItemClick(monetMode) }
)
}
}
}
}
@Composable
private fun MonetModeCheckbox(
selected: Boolean,
onClick: () -> Unit
) {
Box(
modifier = Modifier
.width(72.dp)
.clip(RoundedCornerShape(25))
.clickable(onClick = onClick)
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp)),
contentAlignment = Alignment.Center
) {
Box(
modifier = Modifier
.fillMaxSize()
.aspectRatio(1f)
.padding(8.dp)
.clip(CircleShape)
.background(MaterialTheme.colorScheme.secondary)
.border(1.dp, Color.Black.copy(0.5f), CircleShape),
) {
// Is this bad? Yes. Does it work? Also yes.
Box(modifier = Modifier
.fillMaxHeight(0.5f)
.fillMaxWidth()
.background(MaterialTheme.colorScheme.primary))
Box(modifier = Modifier
.fillMaxHeight(0.5f)
.fillMaxWidth(0.5f)
.background(MaterialTheme.colorScheme.secondaryContainer))
}
AnimatedVisibility(
visible = selected,
enter = fadeIn(tween(250)) + scaleIn(tween(150)),
exit = fadeOut(tween(250)) + scaleOut(tween(150)),
) {
Icon(
imageVector = Icons.Default.Check,
contentDescription = null,
tint = MaterialTheme.colorScheme.inverseOnSurface,
modifier = Modifier
.background(MaterialTheme.colorScheme.inverseSurface, CircleShape)
.padding(4.dp)
)
}
}
}

View File

@ -19,7 +19,7 @@ comGoogleAccompanist = "0.30.1"
androidxRoom = "2.5.1"
comSquareupMoshi = "1.14.0"
comSquareupRetrofit2 = "2.9.0"
comGithubSadellieThemmo = "90842e93f4"
comGithubSadellieThemmo = "ed4063f70f"
orgBurnoutcrewComposereorderable = "0.9.6"
comGithubSadellieExprk = "e55cba8f41"
mxParser = "5.2.1"