feat(ui): improve color scheme selection UI

This commit is contained in:
Nishant Mishra
2025-10-23 19:56:26 +05:30
parent 17293d040c
commit 574c5ec1df
2 changed files with 149 additions and 207 deletions

View File

@@ -1,175 +0,0 @@
/*
* Copyright (c) 2025 Nishant Mishra
*
* This file is part of Tomato - a minimalist pomodoro timer for Android.
*
* Tomato 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.
*
* Tomato 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 Tomato.
* If not, see <https://www.gnu.org/licenses/>.
*/
package org.nsh07.pomodoro.ui.settingsScreen.components
import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.material3.AlertDialogDefaults
import androidx.compose.material3.BasicAlertDialog
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.material3.MaterialTheme.shapes
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEach
import org.nsh07.pomodoro.R
import org.nsh07.pomodoro.ui.theme.TomatoTheme
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun ColorPickerButton(
color: Color,
isSelected: Boolean,
modifier: Modifier = Modifier,
onClick: () -> Unit
) {
IconButton(
shapes = IconButtonDefaults.shapes(),
colors = IconButtonDefaults.iconButtonColors(containerColor = color),
modifier = modifier.size(48.dp),
onClick = onClick
) {
AnimatedContent(isSelected) { isSelected ->
when (isSelected) {
true -> Icon(
painterResource(R.drawable.check),
tint = Color.Black,
contentDescription = null
)
else ->
if (color == Color.White) Icon(
painterResource(R.drawable.colors),
tint = Color.Black,
contentDescription = null
)
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun ColorSchemePickerDialog(
currentColor: Color,
modifier: Modifier = Modifier,
setShowDialog: (Boolean) -> Unit,
onColorChange: (Color) -> Unit,
) {
val colorSchemes = listOf(
Color(0xfffeb4a7), Color(0xffffb3c0), Color(0xfffcaaff), Color(0xffb9c3ff),
Color(0xff62d3ff), Color(0xff44d9f1), Color(0xff52dbc9), Color(0xff78dd77),
Color(0xff9fd75c), Color(0xffc1d02d), Color(0xfffabd00), Color(0xffffb86e),
Color.White
)
BasicAlertDialog(
onDismissRequest = { setShowDialog(false) },
modifier = modifier
) {
Surface(
modifier = Modifier
.wrapContentWidth()
.wrapContentHeight(),
color = colorScheme.surfaceContainer,
shape = shapes.extraLarge,
tonalElevation = AlertDialogDefaults.TonalElevation
) {
Column(modifier = Modifier.padding(24.dp)) {
Text(
text = stringResource(R.string.choose_color_scheme),
style = MaterialTheme.typography.headlineSmall
)
Spacer(Modifier.height(16.dp))
Column(Modifier.align(Alignment.CenterHorizontally)) {
(0..11 step 4).forEach {
Row {
colorSchemes.slice(it..it + 3).fastForEach { color ->
ColorPickerButton(
color,
color == currentColor,
modifier = Modifier.padding(4.dp)
) {
onColorChange(color)
}
}
}
}
ColorPickerButton(
colorSchemes.last(),
colorSchemes.last() == currentColor,
modifier = Modifier.padding(4.dp)
) {
onColorChange(colorSchemes.last())
}
}
Spacer(Modifier.height(24.dp))
TextButton(
shapes = ButtonDefaults.shapes(),
onClick = { setShowDialog(false) },
modifier = Modifier.align(Alignment.End)
) {
Text(stringResource(R.string.ok))
}
}
}
}
}
@Preview
@Composable
fun ColorPickerDialogPreview() {
var currentColor by remember { mutableStateOf(Color(0xfffeb4a7)) }
TomatoTheme(darkTheme = true) {
ColorSchemePickerDialog(
currentColor,
setShowDialog = {},
onColorChange = { currentColor = it }
)
}
}

View File

@@ -17,20 +17,39 @@
package org.nsh07.pomodoro.ui.settingsScreen.components
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.material3.Switch
import androidx.compose.material3.SwitchDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import org.nsh07.pomodoro.R
import org.nsh07.pomodoro.ui.theme.CustomColors.listItemColors
import org.nsh07.pomodoro.ui.theme.CustomColors.switchColors
import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.bottomListItemShape
import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.middleListItemShape
import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.topListItemShape
@Composable
fun ColorSchemePickerListItem(
@@ -40,33 +59,131 @@ fun ColorSchemePickerListItem(
onColorChange: (Color) -> Unit,
modifier: Modifier = Modifier
) {
var showDialog by rememberSaveable { mutableStateOf(false) }
val colorSchemes = listOf(
Color(0xfffeb4a7), Color(0xffffb3c0), Color(0xfffcaaff), Color(0xffb9c3ff),
Color(0xff62d3ff), Color(0xff44d9f1), Color(0xff52dbc9), Color(0xff78dd77),
Color(0xff9fd75c), Color(0xffc1d02d), Color(0xfffabd00), Color(0xffffb86e),
Color.White
)
if (showDialog) {
ColorSchemePickerDialog(
currentColor = color,
setShowDialog = { showDialog = it },
onColorChange = onColorChange
Column(
modifier
.clip(
when (index) {
0 -> topListItemShape
items - 1 -> bottomListItemShape
else -> middleListItemShape
}
)
) {
ListItem(
leadingContent = {
Icon(
painterResource(R.drawable.colors),
null
)
},
headlineContent = { Text("Dynamic color") },
supportingContent = { Text("Adapt theme colors from your wallpaper") },
trailingContent = {
val checked = color == colorSchemes.last()
Switch(
checked = checked,
onCheckedChange = {
if (it) onColorChange(colorSchemes.last())
else onColorChange(colorSchemes.first())
},
thumbContent = {
if (checked) {
Icon(
painter = painterResource(R.drawable.check),
contentDescription = null,
modifier = Modifier.size(SwitchDefaults.IconSize),
)
} else {
Icon(
painter = painterResource(R.drawable.clear),
contentDescription = null,
modifier = Modifier.size(SwitchDefaults.IconSize),
)
}
},
colors = switchColors
)
},
colors = listItemColors,
modifier = Modifier.clip(middleListItemShape)
)
Spacer(Modifier.height(2.dp))
ListItem(
leadingContent = {
Icon(
painter = painterResource(R.drawable.palette),
contentDescription = null,
tint = colorScheme.primary
)
},
headlineContent = { Text(stringResource(R.string.color_scheme)) },
supportingContent = {
Text(
if (color == Color.White) stringResource(R.string.dynamic)
else stringResource(R.string.color)
)
},
colors = listItemColors,
modifier = Modifier.clip(middleListItemShape)
)
}
ClickableListItem(
leadingContent = {
Icon(
painter = painterResource(R.drawable.palette),
contentDescription = null,
tint = colorScheme.primary
)
},
headlineContent = { Text(stringResource(R.string.color_scheme)) },
supportingContent = {
Text(
if (color == Color.White) stringResource(R.string.dynamic)
else stringResource(R.string.color)
)
},
items = items,
index = index,
modifier = modifier.fillMaxWidth()
) { showDialog = true }
}
Column(
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier
.background(listItemColors.containerColor)
.padding(bottom = 8.dp)
) {
LazyRow(contentPadding = PaddingValues(horizontal = 48.dp)) {
items(colorSchemes.dropLast(1)) {
ColorPickerButton(
it,
it == color,
modifier = Modifier.padding(4.dp)
) {
onColorChange(it)
}
}
}
}
}
}
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun ColorPickerButton(
color: Color,
isSelected: Boolean,
modifier: Modifier = Modifier,
onClick: () -> Unit
) {
IconButton(
shapes = IconButtonDefaults.shapes(),
colors = IconButtonDefaults.iconButtonColors(containerColor = color),
modifier = modifier.size(48.dp),
onClick = onClick
) {
AnimatedContent(isSelected) { isSelected ->
when (isSelected) {
true -> Icon(
painterResource(R.drawable.check),
tint = Color.Black,
contentDescription = null
)
else ->
if (color == Color.White) Icon(
painterResource(R.drawable.colors),
tint = Color.Black,
contentDescription = null
)
}
}
}
}