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, themingMode = userPrefs.value.themingMode ?: return,
dynamicThemeEnabled = userPrefs.value.enableDynamicTheme, dynamicThemeEnabled = userPrefs.value.enableDynamicTheme,
amoledThemeEnabled = userPrefs.value.enableAmoledTheme, amoledThemeEnabled = userPrefs.value.enableAmoledTheme,
customColor = userPrefs.value.customColor customColor = userPrefs.value.customColor,
monetMode = userPrefs.value.monetMode
) )
val navController = rememberNavController() val navController = rememberNavController()
val sysUiController = rememberSystemUiController() val sysUiController = rememberSystemUiController()
@ -100,7 +101,7 @@ internal fun UnittoApp() {
Themmo( Themmo(
themmoController = themmoController, themmoController = themmoController,
typography = AppTypography, typography = AppTypography,
animationSpec = tween(150) animationSpec = tween(450)
) { ) {
val statusBarColor = when (currentRoute) { val statusBarColor = when (currentRoute) {
// Match text field container color // 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="enable_dynamic_colors_support">Use colors from your wallpaper</string>
<string name="color_scheme">Color scheme</string> <string name="color_scheme">Color scheme</string>
<string name="selected_color">Selected color</string> <string name="selected_color">Selected color</string>
<string name="monet_mode">Selected style</string>
<!--MISC.--> <!--MISC.-->
<string name="loading_label">Loading…</string> <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.UnitGroup
import com.sadellie.unitto.data.model.UnitsListSorting import com.sadellie.unitto.data.model.UnitsListSorting
import com.sadellie.unitto.data.units.MyUnitIDS import com.sadellie.unitto.data.units.MyUnitIDS
import io.github.sadellie.themmo.MonetMode
import io.github.sadellie.themmo.ThemingMode import io.github.sadellie.themmo.ThemingMode
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
@ -68,6 +69,7 @@ data class UserPreferences(
val enableDynamicTheme: Boolean = false, val enableDynamicTheme: Boolean = false,
val enableAmoledTheme: Boolean = false, val enableAmoledTheme: Boolean = false,
val customColor: Color = Color.Unspecified, val customColor: Color = Color.Unspecified,
val monetMode: MonetMode = MonetMode.TONAL_SPOT,
val digitsPrecision: Int = 3, val digitsPrecision: Int = 3,
val separator: Int = Separator.SPACES, val separator: Int = Separator.SPACES,
val outputFormat: Int = OutputFormat.PLAIN, 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_DYNAMIC_THEME = booleanPreferencesKey("ENABLE_DYNAMIC_THEME_PREF_KEY")
val ENABLE_AMOLED_THEME = booleanPreferencesKey("ENABLE_AMOLED_THEME_PREF_KEY") val ENABLE_AMOLED_THEME = booleanPreferencesKey("ENABLE_AMOLED_THEME_PREF_KEY")
val CUSTOM_COLOR = longPreferencesKey("CUSTOM_COLOR_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 DIGITS_PRECISION = intPreferencesKey("DIGITS_PRECISION_PREF_KEY")
val SEPARATOR = intPreferencesKey("SEPARATOR_PREF_KEY") val SEPARATOR = intPreferencesKey("SEPARATOR_PREF_KEY")
val OUTPUT_FORMAT = intPreferencesKey("OUTPUT_FORMAT_PREF_KEY") val OUTPUT_FORMAT = intPreferencesKey("OUTPUT_FORMAT_PREF_KEY")
@ -119,12 +122,13 @@ class UserPreferencesRepository @Inject constructor(private val dataStore: DataS
} }
} }
.map { preferences -> .map { preferences ->
val themingMode: ThemingMode = val themingMode: ThemingMode = preferences[PrefsKeys.THEMING_MODE]?.let { ThemingMode.valueOf(it) }
preferences[PrefsKeys.THEMING_MODE]?.let { ThemingMode.valueOf(it) } ?: ThemingMode.AUTO
?: ThemingMode.AUTO
val enableDynamicTheme: Boolean = preferences[PrefsKeys.ENABLE_DYNAMIC_THEME] ?: false val enableDynamicTheme: Boolean = preferences[PrefsKeys.ENABLE_DYNAMIC_THEME] ?: false
val enableAmoledTheme: Boolean = preferences[PrefsKeys.ENABLE_AMOLED_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 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 digitsPrecision: Int = preferences[PrefsKeys.DIGITS_PRECISION] ?: 3
val separator: Int = preferences[PrefsKeys.SEPARATOR] ?: Separator.SPACES val separator: Int = preferences[PrefsKeys.SEPARATOR] ?: Separator.SPACES
val outputFormat: Int = preferences[PrefsKeys.OUTPUT_FORMAT] ?: OutputFormat.PLAIN val outputFormat: Int = preferences[PrefsKeys.OUTPUT_FORMAT] ?: OutputFormat.PLAIN
@ -156,6 +160,7 @@ class UserPreferencesRepository @Inject constructor(private val dataStore: DataS
enableDynamicTheme = enableDynamicTheme, enableDynamicTheme = enableDynamicTheme,
enableAmoledTheme = enableAmoledTheme, enableAmoledTheme = enableAmoledTheme,
customColor = customColor, customColor = customColor,
monetMode = monetMode,
digitsPrecision = digitsPrecision, digitsPrecision = digitsPrecision,
separator = separator, separator = separator,
outputFormat = outputFormat, 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. * 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.UserPreferences
import com.sadellie.unitto.data.userprefs.UserPreferencesRepository import com.sadellie.unitto.data.userprefs.UserPreferencesRepository
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import io.github.sadellie.themmo.MonetMode
import io.github.sadellie.themmo.ThemingMode import io.github.sadellie.themmo.ThemingMode
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.first 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 * @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.UnittoListItem
import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar
import com.sadellie.unitto.feature.settings.components.ColorSelector 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.ThemingMode
import io.github.sadellie.themmo.ThemmoController import io.github.sadellie.themmo.ThemmoController
private val colorSchemes: List<Color> by lazy { private val colorSchemes: List<Color> by lazy {
listOf( listOf(
Color(0xFF8A3B31), Color(0xFFF6A1BC),
Color(0xFFA5744A), Color(0xFFFDA387),
Color(0xFF6C8B48), Color(0xFFEAAF60),
Color(0xFF27B089), Color(0xFFC2BD64),
Color(0xFF5C7BAA), Color(0xFF94C78A),
Color(0xFF544F80), Color(0xFF73C9B5),
Color(0xFF9A2A9E), Color(0xFF72C6DA),
Color(0xFF8FBEF2),
Color(0xFFB6B3F6),
Color(0xFFDCA8E4),
) )
} }
@ -93,6 +98,11 @@ internal fun ThemesRoute(
themmoController.setCustomColor(it) themmoController.setCustomColor(it)
viewModel.updateCustomColor(it) viewModel.updateCustomColor(it)
}, },
monetMode = themmoController.currentMonetMode,
onMonetModeChange = {
themmoController.setMonetMode(it)
viewModel.updateMonetMode(it)
}
) )
} }
@ -107,6 +117,8 @@ private fun ThemesScreen(
onAmoledThemeChange: (Boolean) -> Unit, onAmoledThemeChange: (Boolean) -> Unit,
selectedColor: Color, selectedColor: Color,
onColorChange: (Color) -> Unit, onColorChange: (Color) -> Unit,
monetMode: MonetMode,
onMonetModeChange: (MonetMode) -> Unit
) { ) {
val themingModes by remember { val themingModes by remember {
mutableStateOf( 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 = {}, onAmoledThemeChange = {},
selectedColor = Color.Unspecified, selectedColor = Color.Unspecified,
onColorChange = {}, 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" androidxRoom = "2.5.1"
comSquareupMoshi = "1.14.0" comSquareupMoshi = "1.14.0"
comSquareupRetrofit2 = "2.9.0" comSquareupRetrofit2 = "2.9.0"
comGithubSadellieThemmo = "90842e93f4" comGithubSadellieThemmo = "ed4063f70f"
orgBurnoutcrewComposereorderable = "0.9.6" orgBurnoutcrewComposereorderable = "0.9.6"
comGithubSadellieExprk = "e55cba8f41" comGithubSadellieExprk = "e55cba8f41"
mxParser = "5.2.1" mxParser = "5.2.1"