This commit is contained in:
Sad Ellie 2022-08-14 20:38:49 +03:00
parent c965a68198
commit a3a5d9ec27
4 changed files with 178 additions and 253 deletions

View File

@ -23,13 +23,6 @@ import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.widthIn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ArrowDropDown
@ -37,6 +30,7 @@ import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Switch
@ -48,92 +42,12 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.sadellie.unitto.R
/**
* Basic list item for settings screen. By default only has label and support text, clickable.
* This component can be easily modified if you provide additional component to it,
* for example a switch or a checkbox.
*
* @param modifier Modifier that will be applied to a Row.
* @param label Main text.
* @param supportText Text that is located below label.
* @param trailingItem Additional composable, icons for example.
* @param leadingItem Additional composable: buttons, switches, checkboxes or something else.
*/
@Composable
private fun BasicUnittoListItem(
modifier: Modifier = Modifier,
label: String,
supportText: String? = null,
leadingItem: @Composable() (RowScope.() -> Unit) = {},
trailingItem: @Composable() (RowScope.() -> Unit) = {}
) {
Row(
modifier = modifier
.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
leadingItem()
Column(
Modifier.weight(1f), // This makes additional composable to be seen
) {
Text(
modifier = Modifier
.fillMaxWidth(),
text = label,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
supportText?.let {
Text(
modifier = Modifier
.fillMaxWidth(),
text = it,
style = MaterialTheme.typography.bodySmall,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
trailingItem()
}
}
/**
* Represents one item in list on Settings screen.
*
* @param modifier Modifier that will be applied to a Row.
* @param label Main text.
* @param supportText Text that is located below label.
* @param onClick Action to perform when user clicks on this component (whole component is clickable).
*/
@Composable
fun UnittoListItem(
modifier: Modifier = Modifier,
label: String,
supportText: String? = null,
onClick: () -> Unit,
) = BasicUnittoListItem(
modifier = modifier
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(),
onClick = onClick
)
.padding(horizontal = 16.dp, vertical = 12.dp),
label = label,
supportText = supportText
)
/**
* Represents one item in list on Settings screen.
*
@ -149,18 +63,22 @@ fun UnittoListItem(
supportText: String? = null,
switchState: Boolean,
onSwitchChange: (Boolean) -> Unit
) = BasicUnittoListItem(
modifier = Modifier
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(),
onClick = { onSwitchChange(!switchState) }
)
.padding(horizontal = 16.dp, vertical = 12.dp),
label = label,
supportText = supportText
) {
Switch(checked = switchState, onCheckedChange = { onSwitchChange(it) })
ListItem(
modifier = Modifier
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(),
onClick = { onSwitchChange(!switchState) }
),
headlineText = { Text(label) },
supportingText = { supportText?.let { Text(text = it) } },
trailingContent = {
Switch(
checked = switchState,
onCheckedChange = { onSwitchChange(it) })
}
)
}
/**
@ -181,87 +99,60 @@ fun <T> UnittoListItem(
selected: T,
onSelectedChange: (T) -> Unit
) {
BasicUnittoListItem(
modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp),
label = label,
supportText = supportText
) {
var dropDownExpanded by rememberSaveable { mutableStateOf(false) }
var currentOption by rememberSaveable { mutableStateOf(selected) }
val dropDownRotation: Float by animateFloatAsState(
targetValue = if (dropDownExpanded) 180f else 0f,
animationSpec = tween(easing = FastOutSlowInEasing)
)
var dropDownExpanded by rememberSaveable { mutableStateOf(false) }
var currentOption by rememberSaveable { mutableStateOf(selected) }
val dropDownRotation: Float by animateFloatAsState(
targetValue = if (dropDownExpanded) 180f else 0f,
animationSpec = tween(easing = FastOutSlowInEasing)
)
ExposedDropdownMenuBox(
modifier = Modifier,
expanded = dropDownExpanded,
onExpandedChange = { dropDownExpanded = it }
) {
OutlinedTextField(
modifier = Modifier.widthIn(1.dp),
value = allOptions[currentOption] ?: selected.toString(),
onValueChange = {},
readOnly = true,
singleLine = true,
enabled = false,
colors = TextFieldDefaults.outlinedTextFieldColors(
disabledBorderColor = MaterialTheme.colorScheme.outline,
disabledTextColor = MaterialTheme.colorScheme.onSurface,
disabledTrailingIconColor = MaterialTheme.colorScheme.onSurfaceVariant
),
trailingIcon = {
Icon(
imageVector = Icons.Outlined.ArrowDropDown,
modifier = Modifier.rotate(dropDownRotation),
contentDescription = stringResource(R.string.drop_down_description)
)
}
)
ExposedDropdownMenu(
modifier = Modifier.exposedDropdownSize(),
ListItem(
headlineText = { Text(label) },
supportingText = { supportText?.let { Text(text = it) } },
trailingContent = {
ExposedDropdownMenuBox(
modifier = Modifier,
expanded = dropDownExpanded,
onDismissRequest = { dropDownExpanded = false }
onExpandedChange = { dropDownExpanded = it }
) {
allOptions.forEach {
DropdownMenuItem(
text = { Text(it.value) },
onClick = {
currentOption = it.key
onSelectedChange(it.key)
dropDownExpanded = false
}
)
OutlinedTextField(
modifier = Modifier.widthIn(1.dp),
value = allOptions[currentOption] ?: selected.toString(),
onValueChange = {},
readOnly = true,
singleLine = true,
enabled = false,
textStyle = MaterialTheme.typography.bodyLarge,
colors = TextFieldDefaults.outlinedTextFieldColors(
disabledBorderColor = MaterialTheme.colorScheme.outline,
disabledTextColor = MaterialTheme.colorScheme.onSurface,
disabledTrailingIconColor = MaterialTheme.colorScheme.onSurfaceVariant
),
trailingIcon = {
Icon(
imageVector = Icons.Outlined.ArrowDropDown,
modifier = Modifier.rotate(dropDownRotation),
contentDescription = stringResource(R.string.drop_down_description)
)
}
)
ExposedDropdownMenu(
modifier = Modifier.exposedDropdownSize(),
expanded = dropDownExpanded,
onDismissRequest = { dropDownExpanded = false }
) {
allOptions.forEach {
DropdownMenuItem(
text = { Text(it.value) },
onClick = {
currentOption = it.key
onSelectedChange(it.key)
dropDownExpanded = false
}
)
}
}
}
}
}
}
/**
* Composable with leading and trailing items.
*
* @param label Main text.
* @param modifier Modifier that will be applied to a Row.
* @param trailingItem Additional composable, icons for example.
* @param leadingItem Additional composable: buttons, switches, checkboxes or something else.
*/
@Composable
fun UnittoListItem(
label: String,
supportText: String? = null,
modifier: Modifier = Modifier,
paddingValues: PaddingValues = PaddingValues(horizontal = 16.dp, vertical = 12.dp),
leadingItem: @Composable RowScope.() -> Unit = {},
trailingItem: @Composable RowScope.() -> Unit = {}
) {
BasicUnittoListItem(
modifier = modifier.padding(paddingValues),
label = label,
supportText = supportText,
trailingItem = trailingItem,
leadingItem = leadingItem
)
}

View File

@ -26,8 +26,11 @@ import androidx.compose.animation.with
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
@ -36,19 +39,21 @@ import androidx.compose.material.icons.filled.FavoriteBorder
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
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.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.sadellie.unitto.R
import com.sadellie.unitto.data.units.AbstractUnit
import com.sadellie.unitto.screens.common.UnittoListItem
/**
* Represents one list item. Once clicked will navigate up.
@ -68,7 +73,7 @@ private fun BasicUnitListItem(
shortNameLabel: String
) {
var isFavorite: Boolean by rememberSaveable { mutableStateOf(unit.isFavorite) }
Box(
Column(
modifier = Modifier
.clickable(
interactionSource = remember { MutableInteractionSource() },
@ -77,35 +82,56 @@ private fun BasicUnitListItem(
)
.padding(horizontal = 12.dp)
) {
UnittoListItem(
Row(
modifier = Modifier
.background(
if (isSelected) MaterialTheme.colorScheme.secondaryContainer else Color.Transparent,
RoundedCornerShape(24.dp)
),
paddingValues = PaddingValues(horizontal = 12.dp, vertical = 8.dp),
label = stringResource(unit.displayName),
supportText = shortNameLabel,
trailingItem = {
AnimatedContent(
.background(
if (isSelected) MaterialTheme.colorScheme.secondaryContainer else Color.Transparent,
RoundedCornerShape(24.dp)
).padding(paddingValues = PaddingValues(horizontal = 12.dp, vertical = 8.dp))
.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
Column(
Modifier.weight(1f), // This makes additional composable to be seen
) {
Text(
modifier = Modifier
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(false),
onClick = { favoriteAction(unit); isFavorite = !isFavorite }
),
targetState = isFavorite,
transitionSpec = {
(scaleIn() with scaleOut()).using(SizeTransform(clip = false))
}
) {
Icon(
if (unit.isFavorite) Icons.Filled.Favorite else Icons.Filled.FavoriteBorder,
contentDescription = stringResource(R.string.favorite_button_description)
.fillMaxWidth(),
text = stringResource(unit.displayName),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
shortNameLabel.let {
Text(
modifier = Modifier
.fillMaxWidth(),
text = it,
style = MaterialTheme.typography.bodySmall,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
)
AnimatedContent(
modifier = Modifier
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(false),
onClick = { favoriteAction(unit); isFavorite = !isFavorite }
),
targetState = isFavorite,
transitionSpec = {
(scaleIn() with scaleOut()).using(SizeTransform(clip = false))
}
) {
Icon(
if (unit.isFavorite) Icons.Filled.Favorite else Icons.Filled.FavoriteBorder,
contentDescription = stringResource(R.string.favorite_button_description)
)
}
}
}
}

View File

@ -18,12 +18,16 @@
package com.sadellie.unitto.screens.setttings
import androidx.compose.foundation.clickable
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.ListItem
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.platform.LocalContext
import androidx.compose.ui.res.stringResource
import com.sadellie.unitto.BuildConfig
@ -62,53 +66,53 @@ fun SettingsScreen(
// THEME
item {
UnittoListItem(
label = stringResource(R.string.unit_groups_setting),
onClick = { navControllerAction(UNIT_GROUPS_SCREEN) }
ListItem(
headlineText = { Text(stringResource(R.string.unit_groups_setting)) },
modifier = Modifier.clickable { navControllerAction(UNIT_GROUPS_SCREEN) }
)
}
// PRECISION
item {
UnittoListItem(
label = stringResource(R.string.precision_setting),
supportText = stringResource(R.string.precision_setting_support),
onClick = { dialogState = DialogState.PRECISION }
ListItem(
headlineText = { Text(stringResource(R.string.precision_setting)) },
supportingText = { Text(stringResource(R.string.precision_setting_support)) },
modifier = Modifier.clickable { dialogState = DialogState.PRECISION }
)
}
// SEPARATOR
item {
UnittoListItem(
label = stringResource(R.string.separator_setting),
supportText = stringResource(R.string.separator_setting_support),
onClick = { dialogState = DialogState.SEPARATOR }
ListItem(
headlineText = { Text(stringResource(R.string.separator_setting)) },
supportingText = { Text(stringResource(R.string.separator_setting_support)) },
modifier = Modifier.clickable { dialogState = DialogState.SEPARATOR }
)
}
// OUTPUT FORMAT
item {
UnittoListItem(
label = stringResource(R.string.output_format_setting),
supportText = stringResource(R.string.output_format_setting_support),
onClick = { dialogState = DialogState.OUTPUT_FORMAT }
ListItem(
headlineText = { Text(stringResource(R.string.output_format_setting)) },
supportingText = { Text(stringResource(R.string.output_format_setting_support)) },
modifier = Modifier.clickable { dialogState = DialogState.OUTPUT_FORMAT }
)
}
// THEME
item {
UnittoListItem(
label = stringResource(R.string.theme_setting),
supportText = stringResource(R.string.theme_setting_support),
onClick = { navControllerAction(THEMES_SCREEN) }
ListItem(
headlineText = { Text(stringResource(R.string.theme_setting)) },
supportingText = { Text(stringResource(R.string.theme_setting_support)) },
modifier = Modifier.clickable { navControllerAction(THEMES_SCREEN) }
)
}
// CURRENCY RATE NOTE
item {
UnittoListItem(
label = stringResource(R.string.currency_rates_note_setting),
onClick = { dialogState = DialogState.CURRENCY_RATE }
ListItem(
headlineText = { Text(stringResource(R.string.currency_rates_note_setting)) },
modifier = Modifier.clickable { dialogState = DialogState.CURRENCY_RATE }
)
}
@ -117,9 +121,9 @@ fun SettingsScreen(
// TERMS AND CONDITIONS
item {
UnittoListItem(
label = stringResource(R.string.terms_and_conditions),
onClick = {
ListItem(
headlineText = { Text(stringResource(R.string.terms_and_conditions)) },
modifier = Modifier.clickable {
openLink(
mContext,
"http://sadellie.github.io/unitto/terms-app.html"
@ -130,9 +134,9 @@ fun SettingsScreen(
// PRIVACY POLICY
item {
UnittoListItem(
label = stringResource(R.string.privacy_policy),
onClick = {
ListItem(
headlineText = { Text(stringResource(R.string.privacy_policy)) },
modifier = Modifier.clickable {
openLink(
mContext,
"http://sadellie.github.io/unitto/privacy-app.html"
@ -154,28 +158,28 @@ fun SettingsScreen(
// THIRD PARTY
item {
UnittoListItem(
label = stringResource(R.string.third_party_licenses),
onClick = { navControllerAction(ABOUT_SCREEN) }
ListItem(
headlineText = { Text(stringResource(R.string.third_party_licenses)) },
modifier = Modifier.clickable { navControllerAction(ABOUT_SCREEN) }
)
}
// RATE THIS APP
if (BuildConfig.STORE_LINK.isNotEmpty()) {
item {
UnittoListItem(
label = stringResource(R.string.rate_this_app),
onClick = { openLink(mContext, BuildConfig.STORE_LINK) }
ListItem(
headlineText = { Text(stringResource(R.string.rate_this_app)) },
modifier = Modifier.clickable { openLink(mContext, BuildConfig.STORE_LINK) }
)
}
}
// APP VERSION
item {
UnittoListItem(
label = stringResource(R.string.app_version_name_setting),
supportText = "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})",
onClick = {}
ListItem(
headlineText = { Text(stringResource(R.string.app_version_name_setting)) },
supportingText = { Text("${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})") },
modifier = Modifier.clickable {}
)
}
}

View File

@ -33,7 +33,10 @@ import androidx.compose.material.icons.filled.DragIndicator
import androidx.compose.material.icons.filled.RemoveCircle
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem
import androidx.compose.material3.ListItemDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.remember
@ -42,9 +45,8 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.sadellie.unitto.R
import com.sadellie.unitto.screens.common.UnittoLargeTopAppBar
import com.sadellie.unitto.screens.common.Header
import com.sadellie.unitto.screens.common.UnittoListItem
import com.sadellie.unitto.screens.common.UnittoLargeTopAppBar
import org.burnoutcrew.reorderable.ReorderableItem
import org.burnoutcrew.reorderable.detectReorder
import org.burnoutcrew.reorderable.detectReorderAfterLongPress
@ -87,19 +89,21 @@ fun UnitGroupsScreen(
)
val cornerRadius = animateDpAsState(if (isDragging) 16.dp else 0.dp)
UnittoListItem(
ListItem(
headlineText = { Text(stringResource(item.res)) },
modifier = Modifier
.padding(horizontal = cornerRadius.value)
.clip(RoundedCornerShape(cornerRadius.value))
.background(background.value)
.clickable { viewModel.hideUnitGroup(item) }
.detectReorderAfterLongPress(state),
label = stringResource(item.res),
leadingItem = {
colors = ListItemDefaults.colors(
containerColor = background.value
),
leadingContent = {
Icon(
Icons.Default.RemoveCircle,
stringResource(R.string.disable_unit_group_description),
tint= MaterialTheme.colorScheme.outline,
tint = MaterialTheme.colorScheme.outline,
modifier = Modifier.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(false),
@ -107,11 +111,11 @@ fun UnitGroupsScreen(
)
)
},
trailingItem = {
trailingContent = {
Icon(
Icons.Default.DragIndicator,
stringResource(R.string.reorder_unit_group_description),
tint= MaterialTheme.colorScheme.outline,
tint = MaterialTheme.colorScheme.outline,
modifier = Modifier
.clickable(
interactionSource = remember { MutableInteractionSource() },
@ -120,7 +124,7 @@ fun UnitGroupsScreen(
)
.detectReorder(state)
)
}
},
)
}
}
@ -133,17 +137,17 @@ fun UnitGroupsScreen(
}
items(hiddenUnits.value, { it }) {
UnittoListItem(
ListItem(
modifier = Modifier
.background(MaterialTheme.colorScheme.surface)
.clickable { viewModel.returnUnitGroup(it) }
.animateItemPlacement(),
label = stringResource(it.res),
trailingItem = {
headlineText = { Text(stringResource(it.res)) },
trailingContent = {
Icon(
Icons.Default.AddCircleOutline,
stringResource(R.string.enable_unit_group_description),
tint= MaterialTheme.colorScheme.outline,
tint = MaterialTheme.colorScheme.outline,
modifier = Modifier.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(false),