refactor more bottom sheets

This commit is contained in:
MM20
2026-01-20 23:42:15 +01:00
parent c7235df62b
commit c3479bc6bf
11 changed files with 1176 additions and 1096 deletions

View File

@@ -1,16 +1,22 @@
package de.mm20.launcher2.ui.common
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.DockedSearchBar
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.SearchBarDefaults
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -18,6 +24,8 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
@@ -32,81 +40,122 @@ import de.mm20.launcher2.ui.ktx.toPixels
@Composable
fun SearchablePicker(
expanded: Boolean,
value: SavableSearchable?,
onValueChanged: (SavableSearchable?) -> Unit,
onDismissRequest: () -> Unit,
) {
val viewModel: SearchablePickerVM = viewModel()
DismissableBottomSheet(expanded = expanded, onDismissRequest = onDismissRequest) {
val viewModel: SearchablePickerVM = viewModel()
DismissableBottomSheet(onDismissRequest = onDismissRequest) {
Column(
val colorSurface = MaterialTheme.colorScheme.surfaceContainerLow
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(it)
) {
OutlinedTextField(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 12.dp),
value = viewModel.searchQuery,
onValueChange = { viewModel.onSearchQueryChanged(it) },
leadingIcon = {
Icon(painterResource(R.drawable.search_24px), null)
},
placeholder = {
Text(stringResource(R.string.search_bar_placeholder))
},
singleLine = true,
.fillMaxSize(),
contentPadding = PaddingValues(
start = 16.dp,
end = 16.dp,
bottom = 16.dp + WindowInsets.navigationBars.asPaddingValues()
.calculateBottomPadding(),
top = 16.dp,
)
LazyColumn(
modifier = Modifier
.weight(1f)
.fillMaxWidth(),
) {
items(viewModel.items) {
val iconSize = 32.dp.toPixels()
val icon by remember(it.key) {
viewModel.getIcon(
it,
iconSize.toInt()
)
}.collectAsStateWithLifecycle(null)
val selected = it.key == value?.key
Surface(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp),
shape = MaterialTheme.shapes.small,
border = BorderStroke(1.dp, if (selected) MaterialTheme.colorScheme.secondary else MaterialTheme.colorScheme.outline),
color = if (selected) MaterialTheme.colorScheme.secondaryContainer else MaterialTheme.colorScheme.surface,
onClick = { onValueChanged(it) }) {
Row(
modifier = Modifier.padding(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
ShapedLauncherIcon(
icon = { icon },
size = 32.dp,
)
Text(
text = it.label,
modifier = Modifier.weight(1f).padding(horizontal = 16.dp),
style = MaterialTheme.typography.labelMedium,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
if (selected) {
Icon(
painterResource(R.drawable.check_circle_24px_filled),
contentDescription = null,
tint = MaterialTheme.colorScheme.secondary,
) {
stickyHeader {
DockedSearchBar(
modifier = Modifier
.fillMaxWidth()
.drawBehind {
drawRect(
brush = Brush.verticalGradient(
0.5f to colorSurface,
0.5f to colorSurface.copy(alpha = 0f),
)
}
)
}
.padding(bottom = 16.dp),
expanded = false,
onExpandedChange = {},
inputField = {
SearchBarDefaults.InputField(
leadingIcon = {
Icon(
painterResource(R.drawable.search_24px),
contentDescription = null
)
},
trailingIcon = {
if (viewModel.searchQuery.isNotEmpty()) {
IconButton(
onClick = {
viewModel.onSearchQueryChanged("")
}) {
Icon(painterResource(R.drawable.close_24px), null)
}
}
},
onSearch = {},
expanded = false,
onExpandedChange = {},
query = viewModel.searchQuery,
onQueryChange = {
viewModel.onSearchQueryChanged(it)
},
placeholder = {
Text(stringResource(R.string.search_bar_placeholder))
},
)
}
) {
}
}
items(viewModel.items) {
val iconSize = 32.dp.toPixels()
val icon by remember(it.key) {
viewModel.getIcon(
it,
iconSize.toInt()
)
}.collectAsStateWithLifecycle(null)
val selected = it.key == value?.key
Surface(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp),
shape = MaterialTheme.shapes.small,
border = BorderStroke(
1.dp,
if (selected) MaterialTheme.colorScheme.secondary else MaterialTheme.colorScheme.outline
),
color = if (selected) MaterialTheme.colorScheme.secondaryContainer else MaterialTheme.colorScheme.surface,
onClick = { onValueChanged(it) }) {
Row(
modifier = Modifier.padding(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
ShapedLauncherIcon(
icon = { icon },
size = 32.dp,
)
Text(
text = it.label,
modifier = Modifier
.weight(1f)
.padding(horizontal = 16.dp),
style = MaterialTheme.typography.labelMedium,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
if (selected) {
Icon(
painterResource(R.drawable.check_circle_24px_filled),
contentDescription = null,
tint = MaterialTheme.colorScheme.secondary,
)
}
}
}
}
}
}
}

View File

@@ -67,7 +67,7 @@ import kotlinx.coroutines.CancellationException
fun DismissableBottomSheet(
expanded: Boolean = true,
onDismissRequest: () -> Unit,
content: @Composable (contentPadding: PaddingValues) -> Unit,
content: @Composable (contentPadding: PaddingValues) -> Unit,
) {
DismissableBottomSheet(
state = expanded,

View File

@@ -272,19 +272,18 @@ fun CustomizeSearchableSheet(
}
}
}
if (pickIcon) {
DismissableBottomSheet(
onDismissRequest = { viewModel.closeIconPicker() },
) {
IconPicker(
searchable = searchable,
onSelect = {
viewModel.pickIcon(it)
viewModel.closeIconPicker()
},
contentPadding = it,
)
}
DismissableBottomSheet(
expanded = pickIcon,
onDismissRequest = { viewModel.closeIconPicker() },
) {
IconPicker(
searchable = searchable,
onSelect = {
viewModel.pickIcon(it)
viewModel.closeIconPicker()
},
contentPadding = it,
)
}
}
}

View File

@@ -16,6 +16,7 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentHeight
@@ -123,19 +124,18 @@ fun NotesWidget(
}
},
)
if (showConflictResolveSheet) {
NoteWidgetConflictResolveSheet(
localContent = widget.config.storedText,
fileContent = text.text,
onResolve = {
viewModel.resolveFileContentConflict(context, it)
showConflictResolveSheet = false
},
onDismissRequest = {
showConflictResolveSheet = false
}
)
}
NoteWidgetConflictResolveSheet(
expanded = showConflictResolveSheet,
localContent = widget.config.storedText,
fileContent = text.text,
onResolve = {
viewModel.resolveFileContentConflict(context, it)
showConflictResolveSheet = false
},
onDismissRequest = {
showConflictResolveSheet = false
}
)
return
}
Column {
@@ -374,44 +374,44 @@ fun NotesWidget(
}
}
if (readWriteErrorSheetText != null) {
NoteReadWriteErrorSheet(
message = readWriteErrorSheetText!!,
onDismiss = { readWriteErrorSheetText = null },
onRelink = {
linkFileLauncher.launch(
context.getString(
R.string.notes_widget_export_filename,
ZonedDateTime.now().format(
DateTimeFormatter.ISO_INSTANT
)
NoteReadWriteErrorSheet(
message = readWriteErrorSheetText,
onDismiss = { readWriteErrorSheetText = null },
onRelink = {
linkFileLauncher.launch(
context.getString(
R.string.notes_widget_export_filename,
ZonedDateTime.now().format(
DateTimeFormatter.ISO_INSTANT
)
)
},
onUnlink = {
viewModel.unlinkFile(context)
}
)
}
)
},
onUnlink = {
viewModel.unlinkFile(context)
}
)
}
@Composable
fun NoteWidgetConflictResolveSheet(
expanded: Boolean,
localContent: String,
fileContent: String,
onResolve: (LinkedFileConflictStrategy) -> Unit,
onDismissRequest: () -> Unit,
) {
var selectedStrategy by remember { mutableStateOf<LinkedFileConflictStrategy?>(null) }
DismissableBottomSheet(
expanded = expanded,
onDismissRequest = onDismissRequest,
) {
var selectedStrategy by remember { mutableStateOf<LinkedFileConflictStrategy?>(null) }
Column(
modifier = Modifier
.fillMaxWidth()
.verticalScroll(rememberScrollState())
.padding(it),
.padding(16.dp)
.navigationBarsPadding(),
) {
Text(
@@ -538,19 +538,24 @@ fun SelectableNoteContent(
@Composable
fun NoteReadWriteErrorSheet(
message: String,
message: String?,
onDismiss: () -> Unit,
onRelink: () -> Unit,
onUnlink: () -> Unit,
) {
DismissableBottomSheet(
state = message,
expanded = { it != null },
onDismissRequest = onDismiss,
) {
) { message ->
message ?: return@DismissableBottomSheet
Column(
modifier = Modifier
.fillMaxWidth()
.verticalScroll(rememberScrollState())
.padding(it)
.padding(16.dp)
.navigationBarsPadding()
) {
Text(
text = message,

View File

@@ -8,6 +8,7 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
@@ -51,7 +52,8 @@ fun CorePaletteColorPreference(
var showDialog by remember { mutableStateOf(false) }
Row(
modifier = modifier.fillMaxWidth()
modifier = modifier
.fillMaxWidth()
.clip(MaterialTheme.shapes.extraSmall)
.clickable(
onClick = { showDialog = true },
@@ -62,7 +64,9 @@ fun CorePaletteColorPreference(
) {
ColorSwatch(
color = Color(value ?: defaultValue),
modifier = Modifier.padding(end = 20.dp).size(48.dp),
modifier = Modifier
.padding(end = 20.dp)
.size(48.dp),
)
Text(
@@ -74,78 +78,79 @@ fun CorePaletteColorPreference(
)
}
if (showDialog) {
var currentValue by remember { mutableStateOf(value) }
DismissableBottomSheet(onDismissRequest = {
var currentValue by remember { mutableStateOf(value) }
DismissableBottomSheet(
expanded = showDialog,
onDismissRequest = {
onValueChange(currentValue)
showDialog = false
}) {
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
.padding(it)
) {
SwitchPreference(
icon = R.drawable.rule_settings_24px,
title = stringResource(R.string.theme_color_scheme_system_default),
value = currentValue == null,
onValueChanged = {
currentValue = if (it) null else defaultValue
},
containerColor = Color.Transparent,
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
.padding(16.dp)
.navigationBarsPadding()
) {
SwitchPreference(
icon = R.drawable.rule_settings_24px,
title = stringResource(R.string.theme_color_scheme_system_default),
value = currentValue == null,
onValueChanged = {
currentValue = if (it) null else defaultValue
},
containerColor = Color.Transparent,
)
AnimatedVisibility(
currentValue != null,
enter = expandVertically(
expandFrom = Alignment.Top,
),
exit = shrinkVertically(
shrinkTowards = Alignment.Top,
)
AnimatedVisibility(
currentValue != null,
enter = expandVertically(
expandFrom = Alignment.Top,
),
exit = shrinkVertically(
shrinkTowards = Alignment.Top,
) {
Column {
HorizontalDivider(
modifier = Modifier.padding(bottom = 24.dp)
)
) {
Column {
val colorPickerState = rememberHctColorPickerState(
initialColor = Color(value ?: defaultValue),
onColorChanged = {
currentValue = it.toArgb()
}
)
HctColorPicker(
modifier = Modifier
.fillMaxWidth()
.align(Alignment.CenterHorizontally),
state = colorPickerState,
)
if (autoGenerate != null) {
HorizontalDivider(
modifier = Modifier.padding(bottom = 24.dp)
modifier = Modifier.padding(top = 16.dp)
)
val colorPickerState = rememberHctColorPickerState(
initialColor = Color(value ?: defaultValue),
onColorChanged = {
currentValue = it.toArgb()
}
)
HctColorPicker(
TextButton(
modifier = Modifier
.fillMaxWidth()
.align(Alignment.CenterHorizontally),
state = colorPickerState,
)
if (autoGenerate != null) {
HorizontalDivider(
modifier = Modifier.padding(top = 16.dp)
)
TextButton(
modifier = Modifier
.padding(top = 8.dp)
.align(Alignment.End),
contentPadding = ButtonDefaults.TextButtonWithIconContentPadding,
onClick = {
val autoGenerated = autoGenerate()
currentValue = autoGenerated
if (autoGenerated != null) {
colorPickerState.setColor(Color(autoGenerated))
}
.padding(top = 8.dp)
.align(Alignment.End),
contentPadding = ButtonDefaults.TextButtonWithIconContentPadding,
onClick = {
val autoGenerated = autoGenerate()
currentValue = autoGenerated
if (autoGenerated != null) {
colorPickerState.setColor(Color(autoGenerated))
}
) {
Icon(
painterResource(R.drawable.wand_stars_20px), null,
modifier = Modifier
.padding(ButtonDefaults.IconSpacing)
.size(ButtonDefaults.IconSize)
)
Text(stringResource(R.string.theme_color_scheme_autogenerate))
}
) {
Icon(
painterResource(R.drawable.wand_stars_20px), null,
modifier = Modifier
.padding(ButtonDefaults.IconSpacing)
.size(ButtonDefaults.IconSize)
)
Text(stringResource(R.string.theme_color_scheme_autogenerate))
}
}
}

View File

@@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
@@ -74,7 +75,8 @@ fun ThemeColorPreference(
var showDialog by remember { mutableStateOf(false) }
Row(
modifier = modifier.fillMaxWidth()
modifier = modifier
.fillMaxWidth()
.clip(MaterialTheme.shapes.extraSmall)
.clickable(
onClick = { showDialog = true },
@@ -85,7 +87,9 @@ fun ThemeColorPreference(
) {
ColorSwatch(
color = Color((value ?: defaultValue).get(corePalette)),
modifier = Modifier.padding(end = 20.dp).size(48.dp),
modifier = Modifier
.padding(end = 20.dp)
.size(48.dp),
)
Text(
@@ -97,337 +101,338 @@ fun ThemeColorPreference(
)
}
if (showDialog) {
var currentValue by remember { mutableStateOf(value) }
var currentValue by remember { mutableStateOf(value) }
val actualValue = currentValue ?: defaultValue
DismissableBottomSheet(onDismissRequest = {
DismissableBottomSheet(
expanded = showDialog,
onDismissRequest = {
onValueChange(currentValue)
showDialog = false
}) {
Column(
modifier = Modifier
.fillMaxWidth()
.verticalScroll(rememberScrollState())
.padding(it),
val actualValue = currentValue ?: defaultValue
Column(
modifier = Modifier
.fillMaxWidth()
.verticalScroll(rememberScrollState())
.padding(16.dp)
.navigationBarsPadding(),
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(ButtonGroupDefaults.ConnectedSpaceBetween)
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(ButtonGroupDefaults.ConnectedSpaceBetween)
ToggleButton(
modifier = Modifier.weight(1f),
checked = actualValue is ColorRef,
onCheckedChange = {
if (actualValue is ColorRef) return@ToggleButton
currentValue = defaultValue
},
shapes = ButtonGroupDefaults.connectedLeadingButtonShapes()
) {
ToggleButton(
modifier = Modifier.weight(1f),
checked = actualValue is ColorRef,
onCheckedChange = {
if (actualValue is ColorRef) return@ToggleButton
currentValue = defaultValue
},
shapes = ButtonGroupDefaults.connectedLeadingButtonShapes()
) {
Icon(
painterResource(
if (actualValue is ColorRef) R.drawable.check_20px else R.drawable.palette_20px
),
null,
modifier = Modifier
.padding(end = ToggleButtonDefaults.IconSpacing)
.size(ToggleButtonDefaults.IconSize)
)
Text(stringResource(R.string.theme_color_scheme_palette_color))
}
ToggleButton(
modifier = Modifier.weight(1f),
checked = actualValue is StaticColor,
onCheckedChange = {
currentValue = StaticColor(actualValue.get(corePalette))
},
shapes = ButtonGroupDefaults.connectedTrailingButtonShapes()
) {
Icon(
painterResource(
if (actualValue is StaticColor) R.drawable.check_20px else R.drawable.colorize_20px
),
null,
modifier = Modifier
.padding(end = ToggleButtonDefaults.IconSpacing)
.size(ToggleButtonDefaults.IconSize)
)
Text(stringResource(R.string.theme_color_scheme_custom_color))
}
Icon(
painterResource(
if (actualValue is ColorRef) R.drawable.check_20px else R.drawable.palette_20px
),
null,
modifier = Modifier
.padding(end = ToggleButtonDefaults.IconSpacing)
.size(ToggleButtonDefaults.IconSize)
)
Text(stringResource(R.string.theme_color_scheme_palette_color))
}
AnimatedContent(
actualValue,
label = "AnimatedContent",
contentKey = { it is StaticColor }
) { themeColor ->
Column {
if (themeColor is StaticColor) {
val colorPickerState = rememberHctColorPickerState(
initialColor = Color(themeColor.color),
onColorChanged = {
currentValue = StaticColor(it.toArgb())
}
)
HctColorPicker(
modifier = Modifier
.fillMaxWidth()
.padding(top = 24.dp)
.align(Alignment.CenterHorizontally),
state = colorPickerState
)
} else if (themeColor is ColorRef) {
val hct = Hct.fromInt(corePalette.get(themeColor.color))
val hue = hct.hue.toFloat()
val chroma = hct.chroma.toFloat()
var tone by remember(value == null) { mutableStateOf(themeColor.tone.toFloat()) }
Row(
modifier = Modifier.padding(top = 24.dp, bottom = 8.dp)
) {
ColorSwatch(
color = Color(
corePalette
.get(CorePaletteColor.Primary)
.atTone(tone.toInt())
),
modifier = Modifier
.padding(8.dp)
.size(64.dp)
.clickable {
currentValue = ColorRef(
CorePaletteColor.Primary,
tone.roundToInt()
)
},
selected = themeColor.color == CorePaletteColor.Primary,
)
Spacer(modifier = Modifier.weight(1f))
ColorSwatch(
color = Color(
corePalette
.get(CorePaletteColor.Secondary)
.atTone(tone.toInt())
),
modifier = Modifier
.padding(8.dp)
.size(64.dp)
.clickable {
currentValue = ColorRef(
CorePaletteColor.Secondary,
tone.roundToInt()
)
},
selected = themeColor.color == CorePaletteColor.Secondary,
)
Spacer(modifier = Modifier.weight(1f))
ColorSwatch(
color = Color(
corePalette
.get(CorePaletteColor.Tertiary)
.atTone(tone.toInt())
),
modifier = Modifier
.padding(8.dp)
.size(64.dp)
.clickable {
currentValue = ColorRef(
CorePaletteColor.Tertiary,
tone.roundToInt()
)
},
selected = themeColor.color == CorePaletteColor.Tertiary,
)
ToggleButton(
modifier = Modifier.weight(1f),
checked = actualValue is StaticColor,
onCheckedChange = {
currentValue = StaticColor(actualValue.get(corePalette))
},
shapes = ButtonGroupDefaults.connectedTrailingButtonShapes()
) {
Icon(
painterResource(
if (actualValue is StaticColor) R.drawable.check_20px else R.drawable.colorize_20px
),
null,
modifier = Modifier
.padding(end = ToggleButtonDefaults.IconSpacing)
.size(ToggleButtonDefaults.IconSize)
)
Text(stringResource(R.string.theme_color_scheme_custom_color))
}
}
AnimatedContent(
actualValue,
label = "AnimatedContent",
contentKey = { it is StaticColor }
) { themeColor ->
Column {
if (themeColor is StaticColor) {
val colorPickerState = rememberHctColorPickerState(
initialColor = Color(themeColor.color),
onColorChanged = {
currentValue = StaticColor(it.toArgb())
}
Row(
modifier = Modifier.padding(bottom = 16.dp)
) {
ColorSwatch(
color = Color(
corePalette
.get(CorePaletteColor.Neutral)
.atTone(tone.toInt())
),
modifier = Modifier
.padding(8.dp)
.size(64.dp)
.clickable {
currentValue = ColorRef(
CorePaletteColor.Neutral,
tone.roundToInt()
)
},
selected = themeColor.color == CorePaletteColor.Neutral,
)
Spacer(modifier = Modifier.weight(1f))
ColorSwatch(
color = Color(
corePalette
.get(CorePaletteColor.NeutralVariant)
.atTone(tone.toInt())
),
modifier = Modifier
.padding(8.dp)
.size(64.dp)
.clickable {
currentValue = ColorRef(
CorePaletteColor.NeutralVariant,
tone.roundToInt()
)
},
selected = themeColor.color == CorePaletteColor.NeutralVariant,
)
Spacer(modifier = Modifier.weight(1f))
ColorSwatch(
color = Color(
corePalette
.get(CorePaletteColor.Error)
.atTone(tone.toInt())
),
modifier = Modifier
.padding(8.dp)
.size(64.dp)
.clickable {
currentValue = ColorRef(
CorePaletteColor.Error,
tone.roundToInt()
)
},
selected = themeColor.color == CorePaletteColor.Error,
)
}
Row(
verticalAlignment = Alignment.CenterVertically,
)
HctColorPicker(
modifier = Modifier
.fillMaxWidth()
.padding(top = 24.dp)
.align(Alignment.CenterHorizontally),
state = colorPickerState
)
} else if (themeColor is ColorRef) {
val hct = Hct.fromInt(corePalette.get(themeColor.color))
val hue = hct.hue.toFloat()
val chroma = hct.chroma.toFloat()
var tone by remember(value == null) { mutableStateOf(themeColor.tone.toFloat()) }
Row(
modifier = Modifier.padding(top = 24.dp, bottom = 8.dp)
) {
ColorSwatch(
color = Color(
corePalette
.get(CorePaletteColor.Primary)
.atTone(tone.toInt())
),
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp)
) {
Text(
text = "T",
modifier = Modifier.width(32.dp),
style = MaterialTheme.typography.labelMedium,
textAlign = TextAlign.Center
)
Slider(
modifier = Modifier.weight(1f),
value = tone,
valueRange = 0f..100f,
onValueChange = {
tone = it
currentValue = themeColor.copy(tone = it.roundToInt())
.padding(8.dp)
.size(64.dp)
.clickable {
currentValue = ColorRef(
CorePaletteColor.Primary,
tone.roundToInt()
)
},
track = {
Canvas(
modifier = Modifier
.fillMaxWidth()
.height(20.dp)
) {
drawRoundRect(
brush = Brush.horizontalGradient(
colors = listOf(
Color.hct(
hue,
chroma,
0f
),
Color.hct(
hue,
chroma,
10f
),
Color.hct(
hue,
chroma,
20f
),
Color.hct(
hue,
chroma,
30f
),
Color.hct(
hue,
chroma,
40f
),
Color.hct(
hue,
chroma,
50f
),
Color.hct(
hue,
chroma,
60f
),
Color.hct(
hue,
chroma,
70f
),
Color.hct(
hue,
chroma,
80f
),
Color.hct(
hue,
chroma,
90f
),
Color.hct(
hue,
chroma,
100f
),
)
),
style = Fill,
cornerRadius = CornerRadius(
10.dp.toPx(),
10.dp.toPx()
selected = themeColor.color == CorePaletteColor.Primary,
)
Spacer(modifier = Modifier.weight(1f))
ColorSwatch(
color = Color(
corePalette
.get(CorePaletteColor.Secondary)
.atTone(tone.toInt())
),
modifier = Modifier
.padding(8.dp)
.size(64.dp)
.clickable {
currentValue = ColorRef(
CorePaletteColor.Secondary,
tone.roundToInt()
)
},
selected = themeColor.color == CorePaletteColor.Secondary,
)
Spacer(modifier = Modifier.weight(1f))
ColorSwatch(
color = Color(
corePalette
.get(CorePaletteColor.Tertiary)
.atTone(tone.toInt())
),
modifier = Modifier
.padding(8.dp)
.size(64.dp)
.clickable {
currentValue = ColorRef(
CorePaletteColor.Tertiary,
tone.roundToInt()
)
},
selected = themeColor.color == CorePaletteColor.Tertiary,
)
}
Row(
modifier = Modifier.padding(bottom = 16.dp)
) {
ColorSwatch(
color = Color(
corePalette
.get(CorePaletteColor.Neutral)
.atTone(tone.toInt())
),
modifier = Modifier
.padding(8.dp)
.size(64.dp)
.clickable {
currentValue = ColorRef(
CorePaletteColor.Neutral,
tone.roundToInt()
)
},
selected = themeColor.color == CorePaletteColor.Neutral,
)
Spacer(modifier = Modifier.weight(1f))
ColorSwatch(
color = Color(
corePalette
.get(CorePaletteColor.NeutralVariant)
.atTone(tone.toInt())
),
modifier = Modifier
.padding(8.dp)
.size(64.dp)
.clickable {
currentValue = ColorRef(
CorePaletteColor.NeutralVariant,
tone.roundToInt()
)
},
selected = themeColor.color == CorePaletteColor.NeutralVariant,
)
Spacer(modifier = Modifier.weight(1f))
ColorSwatch(
color = Color(
corePalette
.get(CorePaletteColor.Error)
.atTone(tone.toInt())
),
modifier = Modifier
.padding(8.dp)
.size(64.dp)
.clickable {
currentValue = ColorRef(
CorePaletteColor.Error,
tone.roundToInt()
)
},
selected = themeColor.color == CorePaletteColor.Error,
)
}
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp)
) {
Text(
text = "T",
modifier = Modifier.width(32.dp),
style = MaterialTheme.typography.labelMedium,
textAlign = TextAlign.Center
)
Slider(
modifier = Modifier.weight(1f),
value = tone,
valueRange = 0f..100f,
onValueChange = {
tone = it
currentValue = themeColor.copy(tone = it.roundToInt())
},
track = {
Canvas(
modifier = Modifier
.fillMaxWidth()
.height(20.dp)
) {
drawRoundRect(
brush = Brush.horizontalGradient(
colors = listOf(
Color.hct(
hue,
chroma,
0f
),
Color.hct(
hue,
chroma,
10f
),
Color.hct(
hue,
chroma,
20f
),
Color.hct(
hue,
chroma,
30f
),
Color.hct(
hue,
chroma,
40f
),
Color.hct(
hue,
chroma,
50f
),
Color.hct(
hue,
chroma,
60f
),
Color.hct(
hue,
chroma,
70f
),
Color.hct(
hue,
chroma,
80f
),
Color.hct(
hue,
chroma,
90f
),
Color.hct(
hue,
chroma,
100f
),
)
),
style = Fill,
cornerRadius = CornerRadius(
10.dp.toPx(),
10.dp.toPx()
)
}
},
thumb = {
Box(
modifier = Modifier
.padding(vertical = 2.dp, horizontal = 8.dp)
.size(16.dp)
.shadow(1.dp, CircleShape)
.clip(CircleShape)
.background(Color.White)
)
}
)
Text(
text = tone.roundToInt().toString(),
modifier = Modifier.width(32.dp),
style = MaterialTheme.typography.labelMedium,
textAlign = TextAlign.Center,
)
}
}
HorizontalDivider(
modifier = Modifier.padding(top = 16.dp)
)
TextButton(
modifier = Modifier
.padding(top = 8.dp)
.align(Alignment.End),
contentPadding = ButtonDefaults.TextButtonWithIconContentPadding,
onClick = { currentValue = null }
) {
Icon(
painterResource(R.drawable.restart_alt_20px),
null,
modifier = Modifier
.padding(ButtonDefaults.IconSpacing)
.size(ButtonDefaults.IconSize)
},
thumb = {
Box(
modifier = Modifier
.padding(vertical = 2.dp, horizontal = 8.dp)
.size(16.dp)
.shadow(1.dp, CircleShape)
.clip(CircleShape)
.background(Color.White)
)
}
)
Text(
text = tone.roundToInt().toString(),
modifier = Modifier.width(32.dp),
style = MaterialTheme.typography.labelMedium,
textAlign = TextAlign.Center,
)
Text(stringResource(R.string.preference_restore_default))
}
}
HorizontalDivider(
modifier = Modifier.padding(top = 16.dp)
)
TextButton(
modifier = Modifier
.padding(top = 8.dp)
.align(Alignment.End),
contentPadding = ButtonDefaults.TextButtonWithIconContentPadding,
onClick = { currentValue = null }
) {
Icon(
painterResource(R.drawable.restart_alt_20px),
null,
modifier = Modifier
.padding(ButtonDefaults.IconSpacing)
.size(ButtonDefaults.IconSize)
)
Text(stringResource(R.string.preference_restore_default))
}
}
}
}

View File

@@ -285,8 +285,9 @@ fun GesturePreference(
}
}
if (value is GestureAction.Launch && (showAppPicker || app == null)) {
if (value is GestureAction.Launch) {
SearchablePicker(
expanded = (showAppPicker || app == null),
onDismissRequest = {
showAppPicker = false
if (app == null) onValueChanged(GestureAction.NoAction)

View File

@@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
@@ -298,142 +299,141 @@ fun SearchBarStylePreference(
) {
var showDialog by remember { mutableStateOf(false) }
Preference(title = title, summary = summary, onClick = { showDialog = true })
if (showDialog && value != null) {
val styles = remember {
SearchBarStyle.entries
DismissableBottomSheet(
expanded = showDialog,
onDismissRequest = {
showDialog = false
}
) {
val styles = SearchBarStyle.entries
val darkColors =
LocalPreferDarkContentOverWallpaper.current && colors == SearchBarColors.Auto || colors == SearchBarColors.Dark
DismissableBottomSheet(
onDismissRequest = {
showDialog = false
}
Box(
modifier = Modifier
.verticalScroll(rememberScrollState())
.padding(16.dp)
.navigationBarsPadding(),
) {
Box(
modifier = Modifier
.verticalScroll(rememberScrollState())
.padding(it)
Column(
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
Column(
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
for (style in styles) {
Column {
Row(
modifier = Modifier
.padding(top = 8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = stringResource(
when (style) {
SearchBarStyle.Transparent -> R.string.preference_search_bar_style_transparent
SearchBarStyle.Solid -> R.string.preference_search_bar_style_solid
SearchBarStyle.Hidden -> R.string.preference_search_bar_style_hidden
}
),
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.secondary,
modifier = Modifier.weight(1f),
)
IconButton(
onClick = {
onStyleChanged(style)
for (style in styles) {
Column {
Row(
modifier = Modifier
.padding(top = 8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = stringResource(
when (style) {
SearchBarStyle.Transparent -> R.string.preference_search_bar_style_transparent
SearchBarStyle.Solid -> R.string.preference_search_bar_style_solid
SearchBarStyle.Hidden -> R.string.preference_search_bar_style_hidden
}
) {
Icon(
painterResource(
if (style == value) R.drawable.check_circle_24px_filled
else R.drawable.circle_24px
),
contentDescription = null,
tint = if (style == value) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface,
)
),
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.secondary,
modifier = Modifier.weight(1f),
)
IconButton(
onClick = {
onStyleChanged(style)
}
) {
Icon(
painterResource(
if (style == value) R.drawable.check_circle_24px_filled
else R.drawable.circle_24px
),
contentDescription = null,
tint = if (style == value) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface,
)
}
}
Box(
modifier = Modifier
.border(
if (style == value) 4.dp else 2.dp,
if (style == value) MaterialTheme.colorScheme.primary else Color.Transparent,
MaterialTheme.shapes.medium,
)
.clip(MaterialTheme.shapes.medium)
.background(
when {
style != SearchBarStyle.Transparent -> MaterialTheme.colorScheme.inverseSurface
LocalDarkTheme.current != darkColors -> MaterialTheme.colorScheme.surfaceContainer
else -> MaterialTheme.colorScheme.inverseSurface
}
)
.height(IntrinsicSize.Min)
) {
SearchBar(
modifier = Modifier
.padding(16.dp),
level = SearchBarLevel.Resting,
style = style,
value = "",
onValueChange = {},
readOnly = true,
darkColors = darkColors,
)
Box(
modifier = Modifier
.border(
if (style == value) 4.dp else 2.dp,
if (style == value) MaterialTheme.colorScheme.primary else Color.Transparent,
MaterialTheme.shapes.medium,
)
.clip(MaterialTheme.shapes.medium)
.background(
when {
style != SearchBarStyle.Transparent -> MaterialTheme.colorScheme.inverseSurface
LocalDarkTheme.current != darkColors -> MaterialTheme.colorScheme.surfaceContainer
else -> MaterialTheme.colorScheme.inverseSurface
}
)
.height(IntrinsicSize.Min)
.fillMaxSize()
.clickable {
onStyleChanged(style)
}
)
}
if (style == SearchBarStyle.Transparent) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp),
horizontalArrangement = Arrangement.spacedBy(ButtonGroupDefaults.ConnectedSpaceBetween)
) {
SearchBar(
modifier = Modifier
.padding(16.dp),
level = SearchBarLevel.Resting,
style = style,
value = "",
onValueChange = {},
readOnly = true,
darkColors = darkColors,
)
Box(
modifier = Modifier
.fillMaxSize()
.clickable {
onStyleChanged(style)
}
)
}
if (style == SearchBarStyle.Transparent) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp),
horizontalArrangement = Arrangement.spacedBy(ButtonGroupDefaults.ConnectedSpaceBetween)
ToggleButton(
modifier = Modifier.weight(1f),
checked = colors == SearchBarColors.Auto,
onCheckedChange = {
onColorsChanged(SearchBarColors.Auto)
},
shapes = ButtonGroupDefaults.connectedLeadingButtonShapes(),
) {
ToggleButton(
modifier = Modifier.weight(1f),
checked = colors == SearchBarColors.Auto,
onCheckedChange = {
onColorsChanged(SearchBarColors.Auto)
},
shapes = ButtonGroupDefaults.connectedLeadingButtonShapes(),
) {
Icon(
painterResource(R.drawable.auto_awesome_20dp),
contentDescription = null,
)
}
ToggleButton(
modifier = Modifier.weight(1f),
checked = colors == SearchBarColors.Light,
onCheckedChange = {
onColorsChanged(SearchBarColors.Light)
},
shapes = ButtonGroupDefaults.connectedMiddleButtonShapes(),
) {
Icon(
painterResource(R.drawable.light_mode_24px),
contentDescription = null,
)
}
ToggleButton(
modifier = Modifier.weight(1f),
checked = colors == SearchBarColors.Dark,
onCheckedChange = {
onColorsChanged(SearchBarColors.Dark)
},
shapes = ButtonGroupDefaults.connectedTrailingButtonShapes(),
) {
Icon(
painterResource(R.drawable.dark_mode_24px),
contentDescription = null,
)
}
Icon(
painterResource(R.drawable.auto_awesome_20dp),
contentDescription = null,
)
}
ToggleButton(
modifier = Modifier.weight(1f),
checked = colors == SearchBarColors.Light,
onCheckedChange = {
onColorsChanged(SearchBarColors.Light)
},
shapes = ButtonGroupDefaults.connectedMiddleButtonShapes(),
) {
Icon(
painterResource(R.drawable.light_mode_24px),
contentDescription = null,
)
}
ToggleButton(
modifier = Modifier.weight(1f),
checked = colors == SearchBarColors.Dark,
onCheckedChange = {
onColorsChanged(SearchBarColors.Dark)
},
shapes = ButtonGroupDefaults.connectedTrailingButtonShapes(),
) {
Icon(
painterResource(R.drawable.dark_mode_24px),
contentDescription = null,
)
}
}
}

View File

@@ -3,6 +3,7 @@ package de.mm20.launcher2.ui.settings.search
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
@@ -397,27 +398,29 @@ fun SearchSettingsScreen() {
}
}
if (showFilterEditor) {
DismissableBottomSheet(
expanded = showFilterEditor,
onDismissRequest = { showFilterEditor = false }) {
val filters by viewModel.searchFilters.collectAsStateWithLifecycle()
DismissableBottomSheet(onDismissRequest = { showFilterEditor = false }) {
Column(
modifier = Modifier.padding(it)
) {
AnimatedVisibility(filters.allowNetwork) {
SmallMessage(
modifier = Modifier.padding(bottom = 16.dp),
icon = R.drawable.warning_24px,
text = stringResource(R.string.filter_settings_network_warning)
)
}
SearchFilters(
filters = filters,
onFiltersChange = {
viewModel.setSearchFilters(it)
},
settings = true,
Column(
modifier = Modifier
.padding(16.dp)
.navigationBarsPadding()
) {
AnimatedVisibility(filters.allowNetwork) {
SmallMessage(
modifier = Modifier.padding(bottom = 16.dp),
icon = R.drawable.warning_24px,
text = stringResource(R.string.filter_settings_network_warning)
)
}
SearchFilters(
filters = filters,
onFiltersChange = {
viewModel.setSearchFilters(it)
},
settings = true,
)
}
}
}

View File

@@ -77,7 +77,7 @@ import de.mm20.launcher2.themes.shapes.Shape as ThemeShape
@Serializable
data class ShapeSchemeSettingsRoute(
@Serializable(with = UUIDSerializer::class) val id: UUID
): NavKey
) : NavKey
@Composable
fun ShapeSchemeSettingsScreen(themeId: UUID) {
@@ -463,20 +463,38 @@ fun ShapePreference(
)
}
if (showDialog) {
val maxRadius = (24 * factor).toInt()
val maxRadius = (24 * factor).toInt()
val baseTopStart = ((baseShape.radii?.get(0) ?: 12) * factor).toInt()
val baseTopEnd = ((baseShape.radii?.get(1) ?: 12) * factor).toInt()
val baseBottomEnd = ((baseShape.radii?.get(2) ?: 12) * factor).toInt()
val baseBottomStart = ((baseShape.radii?.get(3) ?: 12) * factor).toInt()
val baseTopStart = ((baseShape.radii?.get(0) ?: 12) * factor).toInt()
val baseTopEnd = ((baseShape.radii?.get(1) ?: 12) * factor).toInt()
val baseBottomEnd = ((baseShape.radii?.get(2) ?: 12) * factor).toInt()
val baseBottomStart = ((baseShape.radii?.get(3) ?: 12) * factor).toInt()
var currentCornerStyle by remember(shape) { mutableStateOf(shape?.corners) }
var currentTopStart by remember(shape) { mutableStateOf(shape?.radii?.get(0)) }
var currentTopEnd by remember(shape) { mutableStateOf(shape?.radii?.get(1)) }
var currentBottomEnd by remember(shape) { mutableStateOf(shape?.radii?.get(2)) }
var currentBottomStart by remember(shape) { mutableStateOf(shape?.radii?.get(3)) }
var currentCornerStyle by remember(shape) { mutableStateOf(shape?.corners) }
var currentTopStart by remember(shape) { mutableStateOf(shape?.radii?.get(0)) }
var currentTopEnd by remember(shape) { mutableStateOf(shape?.radii?.get(1)) }
var currentBottomEnd by remember(shape) { mutableStateOf(shape?.radii?.get(2)) }
var currentBottomStart by remember(shape) { mutableStateOf(shape?.radii?.get(3)) }
DismissableBottomSheet(
expanded = showDialog,
onDismissRequest = {
showDialog = false
onValueChange(
ThemeShape(
corners = currentCornerStyle,
radii = if (currentTopStart != null || currentTopEnd != null || currentBottomEnd != null || currentBottomStart != null) {
intArrayOf(
currentTopStart ?: baseTopStart,
currentTopEnd ?: baseTopEnd,
currentBottomEnd ?: baseBottomEnd,
currentBottomStart ?: baseBottomStart,
)
} else null
)
)
}) {
val actualCornerStyle = currentCornerStyle ?: baseShape.corners ?: CornerStyle.Rounded
val actualTopStart = currentTopStart ?: baseTopStart
@@ -484,241 +502,222 @@ fun ShapePreference(
val actualBottomEnd = currentBottomEnd ?: baseBottomEnd
val actualBottomStart = currentBottomStart ?: baseBottomStart
DismissableBottomSheet(
onDismissRequest = {
showDialog = false
onValueChange(
ThemeShape(
corners = currentCornerStyle,
radii = if (currentTopStart != null || currentTopEnd != null || currentBottomEnd != null || currentBottomStart != null) {
intArrayOf(
currentTopStart ?: baseTopStart,
currentTopEnd ?: baseTopEnd,
currentBottomEnd ?: baseBottomEnd,
currentBottomStart ?: baseBottomStart,
)
} else null
)
)
}) {
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
.padding(it)
.padding(bottom = 8.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
.padding(it)
.padding(bottom = 8.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
val previewShape = if ((currentCornerStyle ?: baseShape.corners
?: CornerStyle.Rounded) == CornerStyle.Rounded
) {
val previewShape = if ((currentCornerStyle ?: baseShape.corners
?: CornerStyle.Rounded) == CornerStyle.Rounded
) {
RoundedCornerShape(
topStart = (currentTopStart ?: baseTopStart).dp,
topEnd = (currentTopEnd ?: baseTopEnd).dp,
bottomEnd = (currentBottomEnd ?: baseBottomEnd).dp,
bottomStart = (currentBottomStart ?: baseBottomStart).dp
RoundedCornerShape(
topStart = (currentTopStart ?: baseTopStart).dp,
topEnd = (currentTopEnd ?: baseTopEnd).dp,
bottomEnd = (currentBottomEnd ?: baseBottomEnd).dp,
bottomStart = (currentBottomStart ?: baseBottomStart).dp
)
} else {
CutCornerShape(
topStart = (currentTopStart ?: baseTopStart).dp,
topEnd = (currentTopEnd ?: baseTopEnd).dp,
bottomEnd = (currentBottomEnd ?: baseBottomEnd).dp,
bottomStart = (currentBottomStart ?: baseBottomStart).dp
)
}
Box(
modifier = Modifier
.fillMaxWidth()
.height(max(96, maxRadius * 2).dp)
.background(MaterialTheme.colorScheme.surfaceContainer, previewShape)
.border(
2.dp,
MaterialTheme.colorScheme.primary,
previewShape
)
} else {
CutCornerShape(
topStart = (currentTopStart ?: baseTopStart).dp,
topEnd = (currentTopEnd ?: baseTopEnd).dp,
bottomEnd = (currentBottomEnd ?: baseBottomEnd).dp,
bottomStart = (currentBottomStart ?: baseBottomStart).dp
)
Row(
modifier = Modifier
.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(ButtonGroupDefaults.ConnectedSpaceBetween),
) {
ToggleButton(
modifier = Modifier.weight(1f),
checked = actualCornerStyle == CornerStyle.Rounded,
onCheckedChange = {
currentCornerStyle = CornerStyle.Rounded
},
shapes = ButtonGroupDefaults.connectedLeadingButtonShapes(),
) {
Icon(
painterResource(
if (actualCornerStyle == CornerStyle.Rounded) R.drawable.check_20px else R.drawable.rounded_corner_alt_24px
),
contentDescription = null,
modifier = Modifier
.padding(end = ToggleButtonDefaults.IconSpacing)
.size(ToggleButtonDefaults.IconSize)
)
Text(stringResource(R.string.preference_cards_shape_rounded))
}
Box(
ToggleButton(
modifier = Modifier.weight(1f),
checked = actualCornerStyle == CornerStyle.Cut,
onCheckedChange = {
currentCornerStyle = CornerStyle.Cut
},
shapes = ButtonGroupDefaults.connectedTrailingButtonShapes(),
) {
Icon(
painterResource(
if (actualCornerStyle == CornerStyle.Cut) R.drawable.check_20px else R.drawable.cut_corner_20px
),
contentDescription = null,
modifier = Modifier
.padding(end = ToggleButtonDefaults.IconSpacing)
.size(ToggleButtonDefaults.IconSize)
)
Text(stringResource(R.string.preference_cards_shape_cut))
}
}
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
painterResource(R.drawable.rounded_corner_24px),
null,
modifier = Modifier
.fillMaxWidth()
.height(max(96, maxRadius * 2).dp)
.background(MaterialTheme.colorScheme.surfaceContainer, previewShape)
.border(
2.dp,
MaterialTheme.colorScheme.primary,
previewShape
)
.padding(end = 8.dp)
.rotate(-90f)
)
Slider(
modifier = Modifier
.weight(1f),
value = actualTopStart.toFloat(),
onValueChange = {
currentTopStart = it.toInt()
},
valueRange = 0f..maxRadius.toFloat(),
steps = maxRadius + 1
)
Text(
text = actualTopStart.toString(),
modifier = Modifier.width(32.dp),
style = MaterialTheme.typography.labelMedium,
textAlign = TextAlign.Center,
)
}
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
painterResource(R.drawable.rounded_corner_24px),
null,
modifier = Modifier.padding(end = 8.dp)
)
Slider(
modifier = Modifier
.weight(1f),
value = actualTopEnd.toFloat(),
onValueChange = {
currentTopEnd = it.toInt()
},
valueRange = 0f..maxRadius.toFloat(),
steps = maxRadius + 1,
)
Text(
text = actualTopEnd.toString(),
modifier = Modifier.width(32.dp),
style = MaterialTheme.typography.labelMedium,
textAlign = TextAlign.Center,
)
}
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
painterResource(R.drawable.rounded_corner_24px),
null,
modifier = Modifier
.padding(end = 8.dp)
.rotate(90f)
)
Slider(
modifier = Modifier
.weight(1f),
value = actualBottomEnd.toFloat(),
onValueChange = {
currentBottomEnd = it.toInt()
},
valueRange = 0f..maxRadius.toFloat(),
steps = maxRadius + 1,
)
Text(
text = actualBottomEnd.toString(),
modifier = Modifier.width(32.dp),
style = MaterialTheme.typography.labelMedium,
textAlign = TextAlign.Center,
)
}
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
painterResource(R.drawable.rounded_corner_24px),
null,
modifier = Modifier
.padding(end = 8.dp)
.rotate(180f)
)
Slider(
modifier = Modifier
.weight(1f),
value = actualBottomStart.toFloat(),
onValueChange = {
currentBottomStart = it.toInt()
},
valueRange = 0f..maxRadius.toFloat(),
steps = maxRadius + 1,
)
Text(
text = actualBottomStart.toString(),
modifier = Modifier.width(32.dp),
style = MaterialTheme.typography.labelMedium,
textAlign = TextAlign.Center,
)
Row(
}
HorizontalDivider(
modifier = Modifier.padding(top = 16.dp)
)
TextButton(
modifier = Modifier
.padding(top = 8.dp)
.align(Alignment.End),
contentPadding = ButtonDefaults.TextButtonWithIconContentPadding,
onClick = {
currentTopStart = null
currentTopEnd = null
currentBottomEnd = null
currentBottomStart = null
currentCornerStyle = null
onValueChange(null)
}
) {
Icon(
painterResource(R.drawable.restart_alt_20px), null,
modifier = Modifier
.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(ButtonGroupDefaults.ConnectedSpaceBetween),
) {
ToggleButton(
modifier = Modifier.weight(1f),
checked = actualCornerStyle == CornerStyle.Rounded,
onCheckedChange = {
currentCornerStyle = CornerStyle.Rounded
},
shapes = ButtonGroupDefaults.connectedLeadingButtonShapes(),
) {
Icon(
painterResource(
if (actualCornerStyle == CornerStyle.Rounded) R.drawable.check_20px else R.drawable.rounded_corner_alt_24px
),
contentDescription = null,
modifier = Modifier
.padding(end = ToggleButtonDefaults.IconSpacing)
.size(ToggleButtonDefaults.IconSize)
)
Text(stringResource(R.string.preference_cards_shape_rounded))
}
ToggleButton(
modifier = Modifier.weight(1f),
checked = actualCornerStyle == CornerStyle.Cut,
onCheckedChange = {
currentCornerStyle = CornerStyle.Cut
},
shapes = ButtonGroupDefaults.connectedTrailingButtonShapes(),
) {
Icon(
painterResource(
if (actualCornerStyle == CornerStyle.Cut) R.drawable.check_20px else R.drawable.cut_corner_20px
),
contentDescription = null,
modifier = Modifier
.padding(end = ToggleButtonDefaults.IconSpacing)
.size(ToggleButtonDefaults.IconSize)
)
Text(stringResource(R.string.preference_cards_shape_cut))
}
}
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
painterResource(R.drawable.rounded_corner_24px),
null,
modifier = Modifier
.padding(end = 8.dp)
.rotate(-90f)
)
Slider(
modifier = Modifier
.weight(1f),
value = actualTopStart.toFloat(),
onValueChange = {
currentTopStart = it.toInt()
},
valueRange = 0f..maxRadius.toFloat(),
steps = maxRadius + 1
)
Text(
text = actualTopStart.toString(),
modifier = Modifier.width(32.dp),
style = MaterialTheme.typography.labelMedium,
textAlign = TextAlign.Center,
)
}
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
painterResource(R.drawable.rounded_corner_24px),
null,
modifier = Modifier.padding(end = 8.dp)
)
Slider(
modifier = Modifier
.weight(1f),
value = actualTopEnd.toFloat(),
onValueChange = {
currentTopEnd = it.toInt()
},
valueRange = 0f..maxRadius.toFloat(),
steps = maxRadius + 1,
)
Text(
text = actualTopEnd.toString(),
modifier = Modifier.width(32.dp),
style = MaterialTheme.typography.labelMedium,
textAlign = TextAlign.Center,
)
}
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
painterResource(R.drawable.rounded_corner_24px),
null,
modifier = Modifier
.padding(end = 8.dp)
.rotate(90f)
)
Slider(
modifier = Modifier
.weight(1f),
value = actualBottomEnd.toFloat(),
onValueChange = {
currentBottomEnd = it.toInt()
},
valueRange = 0f..maxRadius.toFloat(),
steps = maxRadius + 1,
)
Text(
text = actualBottomEnd.toString(),
modifier = Modifier.width(32.dp),
style = MaterialTheme.typography.labelMedium,
textAlign = TextAlign.Center,
)
}
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
painterResource(R.drawable.rounded_corner_24px),
null,
modifier = Modifier
.padding(end = 8.dp)
.rotate(180f)
)
Slider(
modifier = Modifier
.weight(1f),
value = actualBottomStart.toFloat(),
onValueChange = {
currentBottomStart = it.toInt()
},
valueRange = 0f..maxRadius.toFloat(),
steps = maxRadius + 1,
)
Text(
text = actualBottomStart.toString(),
modifier = Modifier.width(32.dp),
style = MaterialTheme.typography.labelMedium,
textAlign = TextAlign.Center,
)
}
HorizontalDivider(
modifier = Modifier.padding(top = 16.dp)
.padding(ButtonDefaults.IconSpacing)
.size(ButtonDefaults.IconSize)
)
TextButton(
modifier = Modifier
.padding(top = 8.dp)
.align(Alignment.End),
contentPadding = ButtonDefaults.TextButtonWithIconContentPadding,
onClick = {
currentTopStart = null
currentTopEnd = null
currentBottomEnd = null
currentBottomStart = null
currentCornerStyle = null
onValueChange(null)
}
) {
Icon(
painterResource(R.drawable.restart_alt_20px), null,
modifier = Modifier
.padding(ButtonDefaults.IconSpacing)
.size(ButtonDefaults.IconSize)
)
Text(stringResource(R.string.preference_restore_default))
}
Text(stringResource(R.string.preference_restore_default))
}
}
}

View File

@@ -8,14 +8,20 @@ import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
@@ -758,47 +764,54 @@ private fun FontPreference(
onClick = { showDialog = true },
)
if (showDialog) {
DismissableBottomSheet(
expanded = showDialog,
onDismissRequest = { showDialog = false },
) {
val fonts = remember { fontManager.getInstalledFonts() }
DismissableBottomSheet(
onDismissRequest = { showDialog = false },
LazyColumn(
contentPadding = PaddingValues(
start = 16.dp,
top = 16.dp,
end = 16.dp,
bottom = 16.dp + WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding(),
),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
LazyColumn(contentPadding = it, verticalArrangement = Arrangement.spacedBy(12.dp)) {
if (fonts.builtIn.isNotEmpty()) {
item {
FontPickerCategory(
preview.ExtraShort,
null,
fonts.builtIn,
onFontClick = {
onValueChange(it)
showDialog = false
})
}
if (fonts.builtIn.isNotEmpty()) {
item {
FontPickerCategory(
preview.ExtraShort,
null,
fonts.builtIn,
onFontClick = {
onValueChange(it)
showDialog = false
})
}
if (fonts.deviceDefault.isNotEmpty()) {
item {
FontPickerCategory(
preview.ExtraShort,
stringResource(R.string.font_category_device_default),
fonts.deviceDefault,
onFontClick = {
onValueChange(it)
showDialog = false
})
}
}
if (fonts.deviceDefault.isNotEmpty()) {
item {
FontPickerCategory(
preview.ExtraShort,
stringResource(R.string.font_category_device_default),
fonts.deviceDefault,
onFontClick = {
onValueChange(it)
showDialog = false
})
}
if (fonts.generic.isNotEmpty()) {
item {
FontPickerCategory(
preview.ExtraShort,
stringResource(R.string.font_category_generic),
fonts.generic,
onFontClick = {
onValueChange(it)
showDialog = false
})
}
}
if (fonts.generic.isNotEmpty()) {
item {
FontPickerCategory(
preview.ExtraShort,
stringResource(R.string.font_category_generic),
fonts.generic,
onFontClick = {
onValueChange(it)
showDialog = false
})
}
}
}
@@ -951,209 +964,210 @@ private fun TextStylePreference(
},
onClick = { showDialog = true },
)
if (showDialog) {
var fontFamily by remember(value) {
mutableStateOf(value?.fontFamily)
}
var fontSize by remember(value) {
mutableStateOf(value?.fontSize)
}
var lineHeight by remember(value) {
mutableStateOf(value?.lineHeight)
}
var weight by remember(value) {
mutableStateOf((value?.fontWeight as? ThemeFontWeight.Absolute)?.weight)
}
var letterSpacing by remember(value) {
mutableStateOf(value?.letterSpacing)
}
var fontFamily by remember(value) {
mutableStateOf(value?.fontFamily)
}
var fontSize by remember(value) {
mutableStateOf(value?.fontSize)
}
var lineHeight by remember(value) {
mutableStateOf(value?.lineHeight)
}
var weight by remember(value) {
mutableStateOf((value?.fontWeight as? ThemeFontWeight.Absolute)?.weight)
}
var letterSpacing by remember(value) {
mutableStateOf(value?.letterSpacing)
}
DismissableBottomSheet(
onDismissRequest = {
onValueChange(
ThemeTextStyle(
fontFamily = fontFamily,
fontSize = fontSize,
lineHeight = lineHeight,
fontWeight = weight?.let { ThemeFontWeight.Absolute(it) },
letterSpacing = letterSpacing
)
DismissableBottomSheet(
expanded = showDialog,
onDismissRequest = {
onValueChange(
ThemeTextStyle(
fontFamily = fontFamily,
fontSize = fontSize,
lineHeight = lineHeight,
fontWeight = weight?.let { ThemeFontWeight.Absolute(it) },
letterSpacing = letterSpacing
)
showDialog = false
},
) {
)
showDialog = false
},
) {
val actualFontFamily = fontFamily
?: parentValue?.fontFamily
?: defaultValue?.fontFamily
?: defaultValueParent?.fontFamily!!
val actualFontSize = fontSize
?: parentValue?.fontSize
?: defaultValue?.fontSize
?: defaultValueParent?.fontSize!!
val actualLineHeight = lineHeight
?: parentValue?.lineHeight
?: defaultValue?.lineHeight
?: defaultValueParent?.lineHeight!!
val actualWeight = when {
weight != null -> FontWeight(weight!!)
parentValue?.fontWeight != null -> FontWeight(parentValue.fontWeight!!.weight)
defaultValue?.fontWeight is ThemeFontWeight.Absolute -> FontWeight((defaultValue.fontWeight as ThemeFontWeight.Absolute).weight)
defaultValue?.fontWeight is ThemeFontWeight.Relative &&
defaultValueParent?.fontWeight is ThemeFontWeight.Absolute -> {
FontWeight(
(defaultValueParent.fontWeight as ThemeFontWeight.Absolute).weight +
(defaultValue.fontWeight as ThemeFontWeight.Relative).relativeWeight
)
}
else -> FontWeight.Normal
val actualFontFamily = fontFamily
?: parentValue?.fontFamily
?: defaultValue?.fontFamily
?: defaultValueParent?.fontFamily!!
val actualFontSize = fontSize
?: parentValue?.fontSize
?: defaultValue?.fontSize
?: defaultValueParent?.fontSize!!
val actualLineHeight = lineHeight
?: parentValue?.lineHeight
?: defaultValue?.lineHeight
?: defaultValueParent?.lineHeight!!
val actualWeight = when {
weight != null -> FontWeight(weight!!)
parentValue?.fontWeight != null -> FontWeight(parentValue.fontWeight!!.weight)
defaultValue?.fontWeight is ThemeFontWeight.Absolute -> FontWeight((defaultValue.fontWeight as ThemeFontWeight.Absolute).weight)
defaultValue?.fontWeight is ThemeFontWeight.Relative &&
defaultValueParent?.fontWeight is ThemeFontWeight.Absolute -> {
FontWeight(
(defaultValueParent.fontWeight as ThemeFontWeight.Absolute).weight +
(defaultValue.fontWeight as ThemeFontWeight.Relative).relativeWeight
)
}
val actualLetterSpacing = letterSpacing
?: parentValue?.letterSpacing
?: defaultValue?.letterSpacing
?: defaultValueParent?.letterSpacing!!
Column(
modifier = Modifier.padding(it),
else -> FontWeight.Normal
}
val actualLetterSpacing = letterSpacing
?: parentValue?.letterSpacing
?: defaultValue?.letterSpacing
?: defaultValueParent?.letterSpacing!!
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
.padding(16.dp)
.navigationBarsPadding(),
) {
Box(
modifier = Modifier
.fillMaxWidth()
.heightIn(min = 200.dp),
contentAlignment = Alignment.Center,
) {
Box(
modifier = Modifier
.fillMaxWidth()
.heightIn(min = 200.dp),
contentAlignment = Alignment.Center,
) {
Text(
modifier = Modifier.fillMaxWidth(),
text = previewTexts.TwoLines,
textAlign = TextAlign.Center,
color = MaterialTheme.colorScheme.primary,
fontFamily = fontFamilyOf(context, fonts[actualFontFamily]),
fontSize = actualFontSize.sp,
lineHeight = actualLineHeight.em,
fontWeight = actualWeight,
letterSpacing = actualLetterSpacing.em,
)
}
Row(
modifier = Modifier
.padding(vertical = 16.dp)
.fillMaxWidth()
.horizontalScroll(rememberScrollState()),
horizontalArrangement = Arrangement.spacedBy(16.dp),
) {
for ((name, font) in fonts) {
val f = remember(font) { fontFamilyOf(context, font) }
Column(
modifier = Modifier
.width(56.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
Text(
modifier = Modifier.fillMaxWidth(),
text = previewTexts.TwoLines,
textAlign = TextAlign.Center,
color = MaterialTheme.colorScheme.primary,
fontFamily = fontFamilyOf(context, fonts[actualFontFamily]),
fontSize = actualFontSize.sp,
lineHeight = actualLineHeight.em,
fontWeight = actualWeight,
letterSpacing = actualLetterSpacing.em,
)
}
Row(
modifier = Modifier
.padding(vertical = 16.dp)
.fillMaxWidth()
.horizontalScroll(rememberScrollState()),
horizontalArrangement = Arrangement.spacedBy(16.dp),
) {
for ((name, font) in fonts) {
val f = remember(font) { fontFamilyOf(context, font) }
Column(
modifier = Modifier
.width(56.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
OutlinedIconToggleButton(
modifier = Modifier.size(56.dp),
checked = name == actualFontFamily,
onCheckedChange = {
if (it) fontFamily = name
},
) {
Text(
text = previewTexts.ExtraShort,
fontFamily = f,
style = MaterialTheme.typography.headlineSmall,
textAlign = TextAlign.Center,
)
}
OutlinedIconToggleButton(
modifier = Modifier.size(56.dp),
checked = name == actualFontFamily,
onCheckedChange = {
if (it) fontFamily = name
},
) {
Text(
text = name.capitalize(LocaleList.current),
text = previewTexts.ExtraShort,
fontFamily = f,
style = MaterialTheme.typography.bodySmall,
style = MaterialTheme.typography.headlineSmall,
textAlign = TextAlign.Center,
modifier = Modifier.width(56.dp),
maxLines = 1,
overflow = TextOverflow.MiddleEllipsis,
)
}
Text(
text = name.capitalize(LocaleList.current),
fontFamily = f,
style = MaterialTheme.typography.bodySmall,
textAlign = TextAlign.Center,
modifier = Modifier.width(56.dp),
maxLines = 1,
overflow = TextOverflow.MiddleEllipsis,
)
}
}
SliderRow(
icon = R.drawable.format_bold_24px,
min = 100f,
max = 900f,
step = 100f,
value = actualWeight.weight.toFloat(),
onValueChange = { weight = it.toInt() },
formatValue = {
it.roundToInt().toString()
}
)
SliderRow(
icon = R.drawable.format_size_24px,
min = floor((defaultValue?.fontSize ?: defaultValueParent?.fontSize)!! / 2f),
max = ceil((defaultValue?.fontSize ?: defaultValueParent?.fontSize)!! * 2f),
value = actualFontSize.toFloat(),
onValueChange = {
fontSize = it.roundToInt()
},
formatValue = {
it.roundToInt().toString()
}
)
SliderRow(
icon = R.drawable.format_line_spacing_24px,
min = 0.5f,
max = 2f,
step = 0.05f,
value = actualLineHeight,
onValueChange = { lineHeight = it },
formatValue = {
(it * 100).roundToInt().toString() + "%"
}
)
SliderRow(
icon = R.drawable.format_letter_spacing_24px,
min = -0.25f,
max = 1f,
step = 0.01f,
value = actualLetterSpacing,
onValueChange = { letterSpacing = (it * 100f).roundToInt() / 100f },
formatValue = {
(it * 100).roundToInt().toString() + "%"
}
)
HorizontalDivider(
modifier = Modifier.padding(top = 16.dp)
)
TextButton(
modifier = Modifier
.padding(top = 8.dp)
.align(Alignment.End),
contentPadding = ButtonDefaults.TextButtonWithIconContentPadding,
onClick = {
fontFamily = null
fontSize = null
lineHeight = null
weight = null
letterSpacing = null
onValueChange(null)
}
) {
Icon(
painterResource(R.drawable.restart_alt_20px), null,
modifier = Modifier
.padding(ButtonDefaults.IconSpacing)
.size(ButtonDefaults.IconSize)
)
Text(stringResource(R.string.preference_restore_default))
}
}
}
SliderRow(
icon = R.drawable.format_bold_24px,
min = 100f,
max = 900f,
step = 100f,
value = actualWeight.weight.toFloat(),
onValueChange = { weight = it.toInt() },
formatValue = {
it.roundToInt().toString()
}
)
SliderRow(
icon = R.drawable.format_size_24px,
min = floor((defaultValue?.fontSize ?: defaultValueParent?.fontSize)!! / 2f),
max = ceil((defaultValue?.fontSize ?: defaultValueParent?.fontSize)!! * 2f),
value = actualFontSize.toFloat(),
onValueChange = {
fontSize = it.roundToInt()
},
formatValue = {
it.roundToInt().toString()
}
)
SliderRow(
icon = R.drawable.format_line_spacing_24px,
min = 0.5f,
max = 2f,
step = 0.05f,
value = actualLineHeight,
onValueChange = { lineHeight = it },
formatValue = {
(it * 100).roundToInt().toString() + "%"
}
)
SliderRow(
icon = R.drawable.format_letter_spacing_24px,
min = -0.25f,
max = 1f,
step = 0.01f,
value = actualLetterSpacing,
onValueChange = { letterSpacing = (it * 100f).roundToInt() / 100f },
formatValue = {
(it * 100).roundToInt().toString() + "%"
}
)
HorizontalDivider(
modifier = Modifier.padding(top = 16.dp)
)
TextButton(
modifier = Modifier
.padding(top = 8.dp)
.align(Alignment.End),
contentPadding = ButtonDefaults.TextButtonWithIconContentPadding,
onClick = {
fontFamily = null
fontSize = null
lineHeight = null
weight = null
letterSpacing = null
onValueChange(null)
}
) {
Icon(
painterResource(R.drawable.restart_alt_20px), null,
modifier = Modifier
.padding(ButtonDefaults.IconSpacing)
.size(ButtonDefaults.IconSize)
)
Text(stringResource(R.string.preference_restore_default))
}
}
}
}