Better theming options:

- Improved UI/UX
- Added ability to pick color scheme
This commit is contained in:
Sad Ellie 2023-04-09 00:24:30 +03:00
parent ede1ffe2cd
commit b7127e75a9
8 changed files with 417 additions and 55 deletions

View File

@ -63,7 +63,8 @@ internal fun UnittoApp() {
// Anything below will not be called if theming mode is still loading from DataStore // Anything below will not be called if theming mode is still loading from DataStore
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
) )
val navController = rememberNavController() val navController = rememberNavController()
val sysUiController = rememberSystemUiController() val sysUiController = rememberSystemUiController()

View File

@ -1064,9 +1064,9 @@
<string name="ton_force_short">tf</string> <string name="ton_force_short">tf</string>
<string name="millinewton">Millinewton</string> <string name="millinewton">Millinewton</string>
<string name="millinewton_short">mN</string> <string name="millinewton_short">mN</string>
<string name="attonewton">attonewton</string> <string name="attonewton">Attonewton</string>
<string name="attonewton_short">aN</string> <string name="attonewton_short">aN</string>
<string name="dyne">dyne</string> <string name="dyne">Dyne</string>
<string name="dyne_short">dyn</string> <string name="dyne_short">dyn</string>
<string name="joule_per_meter">Joule/meter</string> <string name="joule_per_meter">Joule/meter</string>
<string name="joule_per_meter_short">J/m</string> <string name="joule_per_meter_short">J/m</string>
@ -1317,10 +1317,13 @@
<string name="force_light_mode">Light</string> <string name="force_light_mode">Light</string>
<string name="force_dark_mode">Dark</string> <string name="force_dark_mode">Dark</string>
<string name="color_theme">Color theme</string> <string name="color_theme">Color theme</string>
<string name="color_theme_support">Pick a theming mode</string>
<string name="force_amoled_mode">AMOLED Dark</string> <string name="force_amoled_mode">AMOLED Dark</string>
<string name="force_amoled_mode_support">Use black background for dark themes</string> <string name="force_amoled_mode_support">Use black background for dark themes</string>
<string name="enable_dynamic_colors">Dynamic colors</string> <string name="enable_dynamic_colors">Dynamic colors</string>
<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="selected_color">Selected color</string>
<!--MISC.--> <!--MISC.-->
<string name="loading_label">Loading…</string> <string name="loading_label">Loading…</string>

View File

@ -0,0 +1,106 @@
/*
* 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.core.ui.common
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.unit.dp
private val GroupRowContainerHeight = 40.dp
private val GroupRowItemMinWidth = 58.dp
@Composable
fun SegmentedButtonRow(
modifier: Modifier = Modifier,
content: @Composable RowScope.() -> Unit
) {
OutlinedCard(
modifier = modifier,
shape = CircleShape
) {
Row(
modifier = Modifier
.height(GroupRowContainerHeight)
.widthIn(min = GroupRowItemMinWidth),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
content()
}
}
}
@Composable
fun RowScope.SegmentedButton(
onClick: () -> Unit,
selected: Boolean,
modifier: Modifier = Modifier,
enabled: Boolean = true,
content: @Composable () -> Unit,
) {
val containerColor =
if (selected)
MaterialTheme.colorScheme.secondaryContainer
else
MaterialTheme.colorScheme.surface
OutlinedButton(
modifier = modifier.weight(1f),
onClick = onClick,
shape = RectangleShape,
colors = ButtonDefaults.outlinedButtonColors(
containerColor = containerColor,
contentColor = contentColorFor(containerColor)
),
enabled = enabled,
contentPadding = PaddingValues(horizontal = 12.dp)
) {
AnimatedVisibility(visible = selected) {
Row {
Icon(
modifier = Modifier.size(18.dp),
imageVector = Icons.Default.Check,
contentDescription = null
)
Spacer(Modifier.width(8.dp))
}
}
content()
}
}

View File

@ -18,12 +18,14 @@
package com.sadellie.unitto.data.userprefs package com.sadellie.unitto.data.userprefs
import androidx.compose.ui.graphics.Color
import androidx.datastore.core.DataStore import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.emptyPreferences import androidx.datastore.preferences.core.emptyPreferences
import androidx.datastore.preferences.core.intPreferencesKey import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.core.longPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.core.stringPreferencesKey
import com.sadellie.unitto.core.base.OutputFormat import com.sadellie.unitto.core.base.OutputFormat
import com.sadellie.unitto.core.base.Separator import com.sadellie.unitto.core.base.Separator
@ -47,6 +49,7 @@ import javax.inject.Inject
* still loading. * still loading.
* @property enableDynamicTheme Use dynamic color scheme * @property enableDynamicTheme Use dynamic color scheme
* @property enableAmoledTheme Use amoled color scheme * @property enableAmoledTheme Use amoled color scheme
* @property customColor Generate custom color scheme from this color.
* @property digitsPrecision Current [PRECISIONS]. Number of digits in fractional part * @property digitsPrecision Current [PRECISIONS]. Number of digits in fractional part
* @property separator Current [Separator] that used to separate thousands * @property separator Current [Separator] that used to separate thousands
* @property outputFormat Current [OutputFormat] that is applied to converted value (not input) * @property outputFormat Current [OutputFormat] that is applied to converted value (not input)
@ -64,6 +67,7 @@ data class UserPreferences(
val themingMode: ThemingMode? = null, val themingMode: ThemingMode? = null,
val enableDynamicTheme: Boolean = false, val enableDynamicTheme: Boolean = false,
val enableAmoledTheme: Boolean = false, val enableAmoledTheme: Boolean = false,
val customColor: Color = Color.Unspecified,
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,
@ -90,6 +94,7 @@ class UserPreferencesRepository @Inject constructor(private val dataStore: DataS
val THEMING_MODE = stringPreferencesKey("THEMING_MODE_PREF_KEY") val THEMING_MODE = stringPreferencesKey("THEMING_MODE_PREF_KEY")
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 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")
@ -117,20 +122,14 @@ class UserPreferencesRepository @Inject constructor(private val dataStore: DataS
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 = val enableDynamicTheme: Boolean = preferences[PrefsKeys.ENABLE_DYNAMIC_THEME] ?: false
preferences[PrefsKeys.ENABLE_DYNAMIC_THEME] ?: false val enableAmoledTheme: Boolean = preferences[PrefsKeys.ENABLE_AMOLED_THEME] ?: false
val enableAmoledTheme: Boolean = val customColor: Color = preferences[PrefsKeys.CUSTOM_COLOR]?.let { Color(it.toULong()) } ?: Color.Unspecified
preferences[PrefsKeys.ENABLE_AMOLED_THEME] ?: false val digitsPrecision: Int = preferences[PrefsKeys.DIGITS_PRECISION] ?: 3
val digitsPrecision: Int = val separator: Int = preferences[PrefsKeys.SEPARATOR] ?: Separator.SPACES
preferences[PrefsKeys.DIGITS_PRECISION] ?: 3 val outputFormat: Int = preferences[PrefsKeys.OUTPUT_FORMAT] ?: OutputFormat.PLAIN
val separator: Int = val latestLeftSideUnit: String = preferences[PrefsKeys.LATEST_LEFT_SIDE] ?: MyUnitIDS.kilometer
preferences[PrefsKeys.SEPARATOR] ?: Separator.SPACES val latestRightSideUnit: String = preferences[PrefsKeys.LATEST_RIGHT_SIDE] ?: MyUnitIDS.mile
val outputFormat: Int =
preferences[PrefsKeys.OUTPUT_FORMAT] ?: OutputFormat.PLAIN
val latestLeftSideUnit: String =
preferences[PrefsKeys.LATEST_LEFT_SIDE] ?: MyUnitIDS.kilometer
val latestRightSideUnit: String =
preferences[PrefsKeys.LATEST_RIGHT_SIDE] ?: MyUnitIDS.mile
val shownUnitGroups: List<UnitGroup> = val shownUnitGroups: List<UnitGroup> =
preferences[PrefsKeys.SHOWN_UNIT_GROUPS]?.let { list -> preferences[PrefsKeys.SHOWN_UNIT_GROUPS]?.let { list ->
// Everything is in hidden (nothing in shown) // Everything is in hidden (nothing in shown)
@ -157,6 +156,7 @@ class UserPreferencesRepository @Inject constructor(private val dataStore: DataS
themingMode = themingMode, themingMode = themingMode,
enableDynamicTheme = enableDynamicTheme, enableDynamicTheme = enableDynamicTheme,
enableAmoledTheme = enableAmoledTheme, enableAmoledTheme = enableAmoledTheme,
customColor = customColor,
digitsPrecision = digitsPrecision, digitsPrecision = digitsPrecision,
separator = separator, separator = separator,
outputFormat = outputFormat, outputFormat = outputFormat,
@ -253,6 +253,17 @@ class UserPreferencesRepository @Inject constructor(private val dataStore: DataS
} }
} }
/**
* Update preference on custom color scheme.
*
* @param color New custom color value.
*/
suspend fun updateCustomColor(color: Color) {
dataStore.edit { preferences ->
preferences[PrefsKeys.CUSTOM_COLOR] = color.value.toLong()
}
}
/** /**
* Update preference on starting screen route. * Update preference on starting screen route.
* *

View File

@ -18,6 +18,7 @@
package com.sadellie.unitto.feature.settings package com.sadellie.unitto.feature.settings
import androidx.compose.ui.graphics.Color
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.sadellie.unitto.core.ui.Formatter import com.sadellie.unitto.core.ui.Formatter
@ -75,6 +76,15 @@ class SettingsViewModel @Inject constructor(
} }
} }
/**
* @see UserPreferencesRepository.updateCustomColor
*/
fun updateCustomColor(color: Color) {
viewModelScope.launch {
userPrefsRepository.updateCustomColor(color)
}
}
/** /**
* @see UserPreferencesRepository.updateDigitsPrecision * @see UserPreferencesRepository.updateDigitsPrecision
*/ */

View File

@ -24,77 +24,123 @@ import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Colorize import androidx.compose.material.icons.filled.Colorize
import androidx.compose.material.icons.filled.DarkMode import androidx.compose.material.icons.filled.DarkMode
import androidx.compose.material.icons.filled.Palette import androidx.compose.material.icons.filled.Palette
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.sadellie.unitto.core.ui.R import com.sadellie.unitto.core.ui.R
import com.sadellie.unitto.core.ui.common.Header
import com.sadellie.unitto.core.ui.common.NavigateUpButton import com.sadellie.unitto.core.ui.common.NavigateUpButton
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.core.ui.common.SegmentedButton
import com.sadellie.unitto.core.ui.common.SegmentedButtonRow
import io.github.sadellie.themmo.ThemingMode import io.github.sadellie.themmo.ThemingMode
import io.github.sadellie.themmo.ThemmoController import io.github.sadellie.themmo.ThemmoController
@Composable @Composable
internal fun ThemesScreen( internal fun ThemesRoute(
navigateUpAction: () -> Unit = {}, navigateUpAction: () -> Unit = {},
themmoController: ThemmoController, themmoController: ThemmoController,
viewModel: SettingsViewModel viewModel: SettingsViewModel
) { ) {
ThemesScreen(
navigateUpAction = navigateUpAction,
currentThemingMode = themmoController.currentThemingMode,
onThemeChange = {
themmoController.setThemingMode(it)
viewModel.updateThemingMode(it)
},
isDynamicThemeEnabled = themmoController.isDynamicThemeEnabled,
onDynamicThemeChange = {
themmoController.enableDynamicTheme(it)
viewModel.updateDynamicTheme(it)
},
isAmoledThemeEnabled = themmoController.isAmoledThemeEnabled,
onAmoledThemeChange = {
themmoController.enableAmoledTheme(it)
viewModel.updateAmoledTheme(it)
},
selectedColor = themmoController.currentCustomColor,
onColorChange = {
themmoController.setCustomColor(it)
viewModel.updateCustomColor(it)
}
)
}
@Composable
private fun ThemesScreen(
navigateUpAction: () -> Unit,
currentThemingMode: ThemingMode,
onThemeChange: (ThemingMode) -> Unit,
isDynamicThemeEnabled: Boolean,
onDynamicThemeChange: (Boolean) -> Unit,
isAmoledThemeEnabled: Boolean,
onAmoledThemeChange: (Boolean) -> Unit,
selectedColor: Color,
onColorChange: (Color) -> Unit,
) {
val themingModes by remember {
mutableStateOf(
mapOf(
ThemingMode.AUTO to R.string.force_auto_mode,
ThemingMode.FORCE_LIGHT to R.string.force_light_mode,
ThemingMode.FORCE_DARK to R.string.force_dark_mode
)
)
}
UnittoScreenWithLargeTopBar( UnittoScreenWithLargeTopBar(
title = stringResource(R.string.theme_setting), title = stringResource(R.string.theme_setting),
navigationIcon = { NavigateUpButton(navigateUpAction) } navigationIcon = { NavigateUpButton(navigateUpAction) }
) { paddingValues -> ) { paddingValues ->
LazyColumn(contentPadding = paddingValues) { LazyColumn(contentPadding = paddingValues) {
item { item {
UnittoListItem( ListItem(
leadingContent = { leadingContent = {
Icon( Icon(
Icons.Default.Palette, Icons.Default.Palette,
stringResource(R.string.color_theme), stringResource(R.string.color_theme),
) )
}, },
label = stringResource(R.string.color_theme), headlineContent = { Text(stringResource(R.string.color_theme)) },
allOptions = mapOf( supportingContent = { Text(stringResource(R.string.color_theme_support)) },
ThemingMode.AUTO to stringResource(R.string.force_auto_mode),
ThemingMode.FORCE_LIGHT to stringResource(R.string.force_light_mode),
ThemingMode.FORCE_DARK to stringResource(R.string.force_dark_mode)
),
selected = themmoController.currentThemingMode,
onSelectedChange = {
themmoController.setThemingMode(it)
viewModel.updateThemingMode(it)
}
) )
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
item { item {
UnittoListItem( SegmentedButtonRow(
leadingContent = { modifier = Modifier.padding(56.dp, 8.dp, 24.dp, 2.dp)
Icon( ) {
Icons.Default.Colorize, themingModes.forEach { (mode, stringRes) ->
stringResource(R.string.enable_dynamic_colors), SegmentedButton(
onClick = { onThemeChange(mode) },
selected = currentThemingMode == mode,
content = { Text(stringResource(stringRes)) }
) )
},
label = stringResource(R.string.enable_dynamic_colors),
supportContent = stringResource(R.string.enable_dynamic_colors_support),
switchState = themmoController.isDynamicThemeEnabled,
onSwitchChange = {
themmoController.enableDynamicTheme(it)
viewModel.updateDynamicTheme(it)
} }
)
} }
} }
item { item {
AnimatedVisibility( AnimatedVisibility(
visible = (themmoController.currentThemingMode != ThemingMode.FORCE_LIGHT), visible = currentThemingMode != ThemingMode.FORCE_LIGHT,
enter = expandVertically() + fadeIn(), enter = expandVertically() + fadeIn(),
exit = shrinkVertically() + fadeOut(), exit = shrinkVertically() + fadeOut(),
) { ) {
@ -107,14 +153,66 @@ internal fun ThemesScreen(
}, },
label = stringResource(R.string.force_amoled_mode), label = stringResource(R.string.force_amoled_mode),
supportContent = stringResource(R.string.force_amoled_mode_support), supportContent = stringResource(R.string.force_amoled_mode_support),
switchState = themmoController.isAmoledThemeEnabled, switchState = isAmoledThemeEnabled,
onSwitchChange = { onSwitchChange = onAmoledThemeChange
themmoController.enableAmoledTheme(it) )
viewModel.updateAmoledTheme(it)
} }
}
item { Header(stringResource(R.string.color_scheme)) }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
item {
UnittoListItem(
leadingContent = {
Icon(
Icons.Default.Colorize,
stringResource(R.string.enable_dynamic_colors),
)
},
label = stringResource(R.string.enable_dynamic_colors),
supportContent = stringResource(R.string.enable_dynamic_colors_support),
switchState = isDynamicThemeEnabled,
onSwitchChange = onDynamicThemeChange
)
}
}
item {
AnimatedVisibility(
visible = !isDynamicThemeEnabled,
enter = expandVertically() + fadeIn(),
exit = shrinkVertically() + fadeOut(),
) {
ListItem(
headlineContent = { Text(stringResource(R.string.selected_color)) },
supportingContent = {
ColorSelector(
modifier = Modifier.padding(top = 12.dp),
selected = selectedColor,
onItemClick = onColorChange
)
},
modifier = Modifier.padding(start = 40.dp)
) )
} }
} }
} }
} }
} }
@Preview
@Composable
private fun Preview() {
ThemesScreen(
navigateUpAction = {},
currentThemingMode = ThemingMode.AUTO,
onThemeChange = {},
isDynamicThemeEnabled = false,
onDynamicThemeChange = {},
isAmoledThemeEnabled = false,
onAmoledThemeChange = {},
selectedColor = Color.Unspecified,
onColorChange = {}
)
}

View File

@ -0,0 +1,133 @@
/*
* 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.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyRow
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.surfaceColorAtElevation
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.luminance
import androidx.compose.ui.unit.dp
@Composable
internal fun ColorSelector(
modifier: Modifier = Modifier,
selected: Color,
onItemClick: (Color) -> Unit,
) {
val colorSchemes: List<Color> by remember {
mutableStateOf(
listOf(
Color(0xFFE91E63),
Color(0xFFFF9800),
Color(0xFF4CAF50),
Color(0xFF2196F3),
Color(0xFF9C27B0),
Color(0xFF5C76AA),
Color(0xFF756FAA),
Color(0xFF9E6C2A),
)
)
}
LazyRow(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
// Default, Unitto colors
item {
ColorCheckbox(
color = Color(0xFF186c31),
selected = Color.Unspecified == selected,
onClick = { onItemClick(Color.Unspecified) }
)
}
colorSchemes.forEach {
item {
ColorCheckbox(
color = it,
selected = it == selected,
onClick = { onItemClick(it) }
)
}
}
}
}
@Composable
private fun ColorCheckbox(
color: Color,
selected: Boolean,
onClick: () -> Unit
) {
Box(
modifier = Modifier
.clickable(onClick = onClick)
.background(
MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp),
RoundedCornerShape(25)
)
.width(72.dp)
.aspectRatio(1f),
contentAlignment = Alignment.Center
) {
Box(
modifier = Modifier
.background(color, CircleShape)
.size(54.dp)
.border(1.dp, Color.Black.copy(0.5f), CircleShape)
)
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 = if (color.luminance() > 0.5) Color.Black else Color.White,
)
}
}
}

View File

@ -28,7 +28,7 @@ import com.sadellie.unitto.core.base.TopLevelDestinations
import com.sadellie.unitto.feature.settings.AboutScreen import com.sadellie.unitto.feature.settings.AboutScreen
import com.sadellie.unitto.feature.settings.SettingsScreen import com.sadellie.unitto.feature.settings.SettingsScreen
import com.sadellie.unitto.feature.settings.SettingsViewModel import com.sadellie.unitto.feature.settings.SettingsViewModel
import com.sadellie.unitto.feature.settings.ThemesScreen import com.sadellie.unitto.feature.settings.ThemesRoute
import com.sadellie.unitto.feature.settings.ThirdPartyLicensesScreen import com.sadellie.unitto.feature.settings.ThirdPartyLicensesScreen
import com.sadellie.unitto.feature.settings.UnitGroupsScreen import com.sadellie.unitto.feature.settings.UnitGroupsScreen
import io.github.sadellie.themmo.ThemmoController import io.github.sadellie.themmo.ThemmoController
@ -63,7 +63,7 @@ fun NavGraphBuilder.settingGraph(
} }
composable(themesRoute) { composable(themesRoute) {
ThemesScreen( ThemesRoute(
navigateUpAction = { navController.navigateUp() }, navigateUpAction = { navController.navigateUp() },
themmoController = themmoController, themmoController = themmoController,
viewModel = settingsViewModel viewModel = settingsViewModel