Location: add icons for available payment methods (#1408)

* add backend for PaymentMethod.kt

* change type of acceptedPaymentMethods to Map<PaymentMethod, Boolean>

* add icons to UI and reduce PaymentMethod.kt to Cash and Card

* make OSM parsing for acceptedPaymentMethods correct (in an opinionated way)

* use Toll and TollOff icons

* fix seperator on empty payment methods

* fix sharedElement and animateEnterExit labels that are not part of the sharedElement

* (feat) shape schemes

---------

Co-authored-by: MM20 <15646950+MM2-0@users.noreply.github.com>
This commit is contained in:
shtrophic
2025-06-01 17:27:18 +02:00
committed by GitHub
co-authored by MM20
parent 1efffcf586
commit 78fa1d71dc
50 changed files with 2313 additions and 965 deletions
@@ -40,7 +40,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.themes.Theme
import de.mm20.launcher2.themes.Colors
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.BottomSheetDialog
import de.mm20.launcher2.ui.component.LargeMessage
@@ -63,7 +63,7 @@ fun ImportThemeSheet(
viewModel.readTheme(context, uri)
}
val theme by viewModel.theme
val theme by viewModel.colors
val error by viewModel.error
var apply by viewModel.apply
@@ -132,13 +132,13 @@ fun ImportThemeSheet(
@Composable
fun ThemePreview(
theme: Theme,
colors: Colors,
modifier: Modifier = Modifier,
) {
val darkMode = LocalDarkTheme.current
var darkTheme by remember { mutableStateOf(darkMode) }
val colorScheme = if (darkTheme) darkColorSchemeOf(theme) else lightColorSchemeOf(theme)
val colorScheme = if (darkTheme) darkColorSchemeOf(colors) else lightColorSchemeOf(colors)
Column(modifier = modifier) {
Row(
@@ -146,7 +146,7 @@ fun ThemePreview(
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = theme.name,
text = colors.name,
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.weight(1f),
color = MaterialTheme.colorScheme.onSurfaceVariant
@@ -5,11 +5,11 @@ import android.net.Uri
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import de.mm20.launcher2.preferences.ThemeDescriptor
import de.mm20.launcher2.preferences.ColorsDescriptor
import de.mm20.launcher2.preferences.ui.UiSettings
import de.mm20.launcher2.themes.Theme
import de.mm20.launcher2.themes.Colors
import de.mm20.launcher2.themes.ThemeRepository
import de.mm20.launcher2.themes.fromJson
import de.mm20.launcher2.themes.fromLegacyJson
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.koin.core.component.KoinComponent
@@ -20,12 +20,12 @@ class ImportThemeSheetVM : ViewModel(), KoinComponent {
private val themeRepository: ThemeRepository by inject()
private val uiSettings: UiSettings by inject()
val theme = mutableStateOf<Theme?>(null)
val colors = mutableStateOf<Colors?>(null)
val error = mutableStateOf<Boolean>(false)
val apply = mutableStateOf<Boolean>(false)
fun import() {
val theme = theme.value
val theme = colors.value
val apply = apply.value
if (theme != null) {
viewModelScope.launch {
@@ -37,30 +37,30 @@ class ImportThemeSheetVM : ViewModel(), KoinComponent {
fun readTheme(context: Context, uri: Uri) {
error.value = false
theme.value = null
colors.value = null
apply.value = true
viewModelScope.launch(Dispatchers.IO) {
val inputStream =
context.contentResolver.openInputStream(uri) ?: return@launch
val theme = inputStream.use {
val colors = inputStream.use {
val json = it.readBytes().toString(Charsets.UTF_8)
try {
Theme.fromJson(json)
Colors.fromLegacyJson(json)
} catch (e: IllegalArgumentException) {
null
}
}
this@ImportThemeSheetVM.theme.value = theme
if (theme == null) {
this@ImportThemeSheetVM.colors.value = colors
if (colors == null) {
error.value = true
}
}
}
private fun importTheme(theme: Theme, apply: Boolean) {
themeRepository.createTheme(theme)
private fun importTheme(colors: Colors, apply: Boolean) {
themeRepository.createColors(colors)
if (apply) {
uiSettings.setTheme(ThemeDescriptor.Custom(theme.id.toString()))
uiSettings.setColors(ColorsDescriptor.Custom(colors.id.toString()))
}
}
}
@@ -21,7 +21,6 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
@@ -49,6 +48,9 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
import androidx.compose.material.icons.automirrored.rounded.NavigateNext
import androidx.compose.material.icons.automirrored.rounded.OpenInNew
import androidx.compose.material.icons.outlined.CreditCardOff
import androidx.compose.material.icons.outlined.CreditScore
import androidx.compose.material.icons.outlined.Toll
import androidx.compose.material.icons.rounded.AirplanemodeActive
import androidx.compose.material.icons.rounded.BugReport
import androidx.compose.material.icons.rounded.Commute
@@ -92,9 +94,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.TransformOrigin
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextAlign
@@ -102,7 +102,6 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntRect
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.TextUnitType
import androidx.compose.ui.unit.dp
@@ -115,6 +114,7 @@ import blend.Blend.harmonize
import coil.compose.AsyncImage
import de.mm20.launcher2.i18n.R
import de.mm20.launcher2.icons.CableCar
import de.mm20.launcher2.icons.TollOff
import de.mm20.launcher2.ktx.tryStartActivity
import de.mm20.launcher2.search.Location
import de.mm20.launcher2.search.isOpen
@@ -124,6 +124,7 @@ import de.mm20.launcher2.search.location.LineNameComparator
import de.mm20.launcher2.search.location.LineType
import de.mm20.launcher2.search.location.OpeningHours
import de.mm20.launcher2.search.location.OpeningSchedule
import de.mm20.launcher2.search.location.PaymentMethod
import de.mm20.launcher2.search.location.isNotEmpty
import de.mm20.launcher2.ui.base.LocalTime
import de.mm20.launcher2.ui.component.DefaultToolbarAction
@@ -229,24 +230,33 @@ fun LocationItem(
)
val formattedDistance =
distance?.metersToLocalizedString(context, imperialUnits)
val isOpenString = location.openingSchedule?.isOpen()
?.let { stringResource(if (it) R.string.location_open else R.string.location_closed) }
val sublabel = listOf(location.category, formattedDistance, isOpenString)
val sublabel = listOf(location.category, formattedDistance)
.fastFilterNotNull()
.joinToString("")
val isOpenString = location.openingSchedule?.isOpen()
?.let { stringResource(if (it) R.string.location_open else R.string.location_closed) }
if (sublabel.isNotBlank()) {
Text(
sublabel,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.secondary,
modifier = Modifier
.padding(top = 2.dp)
.sharedElement(
rememberSharedContentState("sublabel"),
this@AnimatedContent
)
)
Row(modifier = Modifier.padding(top = 2.dp)) {
if (sublabel.isNotBlank()) {
Text(
sublabel,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.secondary,
modifier = Modifier
.sharedElement(
rememberSharedContentState("sublabel"),
this@AnimatedContent
)
)
}
if (!isOpenString.isNullOrBlank()) {
Text(
"$isOpenString",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.secondary,
modifier = Modifier.animateEnterExit()
)
}
}
}
Compass(
@@ -324,28 +334,55 @@ fun LocationItem(
this@AnimatedContent
)
)
val category = location.category
val formattedDistance = distance?.metersToLocalizedString(
context, imperialUnits
)
if (category != null || formattedDistance != null) {
Text(
when {
category != null && formattedDistance != null -> "$category$formattedDistance"
category != null -> category
formattedDistance != null -> formattedDistance
else -> ""
},
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.secondary,
modifier = Modifier
.padding(top = 2.dp)
.sharedElement(
rememberSharedContentState("sublabel"),
this@AnimatedContent
)
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(top = 2.dp)
) {
val category = location.category
val acceptedPaymentMethods = location.acceptedPaymentMethods
val formattedDistance = distance?.metersToLocalizedString(
context, imperialUnits
)
if (category != null || formattedDistance != null) {
Text(
when {
category != null && formattedDistance != null -> "$category$formattedDistance"
category != null -> category
formattedDistance != null -> formattedDistance
else -> ""
},
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.secondary,
modifier = Modifier
.sharedElement(
rememberSharedContentState("sublabel"),
this@AnimatedContent
)
)
}
if (!acceptedPaymentMethods.isNullOrEmpty()) {
Text(
"",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.secondary,
modifier = Modifier.animateEnterExit()
)
for ((method, available) in acceptedPaymentMethods) {
Icon(
when (method) {
PaymentMethod.Cash -> if (available) Icons.Outlined.Toll else Icons.Outlined.TollOff
PaymentMethod.Card -> if (available) Icons.Outlined.CreditScore else Icons.Outlined.CreditCardOff
},
contentDescription = null,
tint = MaterialTheme.colorScheme.secondary,
modifier = Modifier
.size(14.5.dp)
.padding(end = 2.dp)
.animateEnterExit()
)
}
}
}
if (location.userRating != null) {
RatingBar(
@@ -75,6 +75,7 @@ import de.mm20.launcher2.search.location.Departure
import de.mm20.launcher2.search.location.LineType
import de.mm20.launcher2.search.location.LocationIcon
import de.mm20.launcher2.search.location.OpeningSchedule
import de.mm20.launcher2.search.location.PaymentMethod
import de.mm20.launcher2.ui.ktx.DegreesConverter
import de.mm20.launcher2.ui.ktx.contrast
import de.mm20.launcher2.ui.ktx.hue
@@ -505,6 +506,9 @@ private object MockLocation : Location {
override val openingSchedule: OpeningSchedule =
OpeningSchedule.TwentyFourSeven
override val acceptedPaymentMethods: Map<PaymentMethod, Boolean>?
get() = mapOf(PaymentMethod.Card to true, PaymentMethod.Cash to false)
override val websiteUrl: String = "https://en.wikipedia.org/wiki/Brandenburg_Gate"
override val phoneNumber: String = "+49 1234567"
@@ -28,7 +28,7 @@ fun Modifier.verticalFadingEdges(
if(!enabled) return this
if (top == 0.dp && bottom == 0.dp) return this
return this then drawWithContent {
return drawWithContent {
val topColors = if (top > 0.dp) createColors(
1f - amount,
@@ -27,7 +27,7 @@ fun Modifier.verticalScrims(
if (!enabled) return this
if (top == 0.dp && bottom == 0.dp) return this
return this then drawWithCache {
return drawWithCache {
onDrawWithContent {
val topColors = if (top > 0.dp) createColors(
1f - amount,
@@ -42,8 +42,8 @@ import de.mm20.launcher2.ui.settings.buildinfo.BuildInfoSettingsScreen
import de.mm20.launcher2.ui.settings.calendarsearch.CalendarProviderSettingsScreen
import de.mm20.launcher2.ui.settings.calendarsearch.CalendarSearchSettingsScreen
import de.mm20.launcher2.ui.settings.cards.CardsSettingsScreen
import de.mm20.launcher2.ui.settings.colorscheme.ThemeSettingsScreen
import de.mm20.launcher2.ui.settings.colorscheme.ThemesSettingsScreen
import de.mm20.launcher2.ui.settings.colorscheme.ColorSchemeSettingsScreen
import de.mm20.launcher2.ui.settings.colorscheme.ColorSchemesSettingsScreen
import de.mm20.launcher2.ui.settings.contacts.ContactsSettingsScreen
import de.mm20.launcher2.ui.settings.crashreporter.CrashReportScreen
import de.mm20.launcher2.ui.settings.crashreporter.CrashReporterScreen
@@ -69,6 +69,8 @@ import de.mm20.launcher2.ui.settings.plugins.PluginSettingsScreen
import de.mm20.launcher2.ui.settings.plugins.PluginsSettingsScreen
import de.mm20.launcher2.ui.settings.search.SearchSettingsScreen
import de.mm20.launcher2.ui.settings.searchactions.SearchActionsSettingsScreen
import de.mm20.launcher2.ui.settings.shapes.ShapeSchemeSettingsScreen
import de.mm20.launcher2.ui.settings.shapes.ShapeSchemesSettingsScreen
import de.mm20.launcher2.ui.settings.tags.TagsSettingsScreen
import de.mm20.launcher2.ui.settings.tasks.TasksIntegrationSettingsScreen
import de.mm20.launcher2.ui.settings.unitconverter.UnitConverterHelpSettingsScreen
@@ -160,11 +162,11 @@ class SettingsActivity : BaseActivity() {
composable("settings/icons") {
IconsSettingsScreen()
}
composable("settings/appearance/themes") {
ThemesSettingsScreen()
composable("settings/appearance/colors") {
ColorSchemesSettingsScreen()
}
composable(
"settings/appearance/themes/{id}",
"settings/appearance/colors/{id}",
arguments = listOf(navArgument("id") {
nullable = false
})
@@ -172,7 +174,21 @@ class SettingsActivity : BaseActivity() {
val id = it.arguments?.getString("id")?.let {
UUID.fromString(it)
} ?: return@composable
ThemeSettingsScreen(id)
ColorSchemeSettingsScreen(id)
}
composable("settings/appearance/shapes") {
ShapeSchemesSettingsScreen()
}
composable(
"settings/appearance/shapes/{id}",
arguments = listOf(navArgument("id") {
nullable = false
})
) {
val id = it.arguments?.getString("id")?.let {
UUID.fromString(it)
} ?: return@composable
ShapeSchemeSettingsScreen(id)
}
composable("settings/appearance/cards") {
CardsSettingsScreen()
@@ -1,5 +1,10 @@
package de.mm20.launcher2.ui.settings.appearance
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.CropSquare
import androidx.compose.material.icons.rounded.Palette
import androidx.compose.material.icons.rounded.TextFields
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
@@ -45,12 +50,17 @@ fun AppearanceSettingsScreen() {
viewModel.setColorScheme(newValue)
}
)
}
}
item {
PreferenceCategory {
Preference(
title = stringResource(id = R.string.preference_screen_colors),
summary = themeName,
onClick = {
navController?.navigate("settings/appearance/themes")
}
navController?.navigate("settings/appearance/colors")
},
icon = Icons.Rounded.Palette,
)
val font by viewModel.font.collectAsState()
ListPreference(
@@ -68,7 +78,16 @@ fun AppearanceSettingsScreen() {
getTypography(context, it.value)
}
Text(it.first, style = typography.titleMedium)
}
},
icon = Icons.Rounded.TextFields,
)
Preference(
title = stringResource(id = R.string.preference_screen_shapes),
summary = themeName,
onClick = {
navController?.navigate("settings/appearance/shapes")
},
icon = Icons.Rounded.CropSquare,
)
Preference(
@@ -2,7 +2,6 @@ package de.mm20.launcher2.ui.settings.appearance
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import de.mm20.launcher2.icons.IconService
import de.mm20.launcher2.preferences.ColorScheme
import de.mm20.launcher2.preferences.Font
import de.mm20.launcher2.preferences.ui.UiSettings
@@ -26,8 +25,8 @@ class AppearanceSettingsScreenVM : ViewModel(), KoinComponent {
uiSettings.setColorScheme(colorScheme)
}
val themeName = uiSettings.theme.flatMapLatest {
themeRepository.getThemeOrDefault(it)
val themeName = uiSettings.colors.flatMapLatest {
themeRepository.getColorsOrDefault(it)
}.map {
it.name
}
@@ -47,28 +47,6 @@ fun CardsSettingsScreen() {
}
item {
PreferenceCategory {
ListPreference(
icon = Icons.Rounded.Rectangle,
title = stringResource(R.string.preference_cards_shape),
items = listOf(
stringResource(R.string.preference_cards_shape_rounded) to SurfaceShape.Rounded,
stringResource(R.string.preference_cards_shape_cut) to SurfaceShape.Cut,
),
value = cardStyle.shape,
onValueChanged = {
viewModel.setShape(it)
})
SliderPreference(
title = stringResource(R.string.preference_cards_corner_radius),
icon = Icons.Rounded.RoundedCorner,
value = cardStyle.cornerRadius,
min = 0,
max = 24,
step = 1,
onValueChanged = {
viewModel.setRadius(it)
}
)
SliderPreference(
title = stringResource(R.string.preference_cards_opacity),
icon = Icons.Rounded.Opacity,
@@ -1,7 +1,6 @@
package de.mm20.launcher2.ui.settings.cards
import androidx.lifecycle.ViewModel
import de.mm20.launcher2.preferences.SurfaceShape
import de.mm20.launcher2.preferences.ui.UiSettings
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
@@ -15,15 +14,8 @@ class CardsSettingsScreenVM : ViewModel(), KoinComponent {
uiSettings.setCardOpacity(opacity)
}
fun setRadius(radius: Int) {
uiSettings.setCardRadius(radius)
}
fun setBorderWidth(borderWidth: Int) {
uiSettings.setCardBorderWidth(borderWidth)
}
fun setShape(shape: SurfaceShape) {
uiSettings.setCardShape(shape)
}
}
@@ -1,16 +1,11 @@
package de.mm20.launcher2.ui.settings.colorscheme
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.FlowRowScope
import androidx.compose.foundation.layout.IntrinsicSize
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.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.DarkMode
@@ -31,7 +26,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@Composable
fun ThemePreferenceCategory(
fun ColorSchemePreferenceCategory(
title: String,
previewColorScheme: ColorScheme,
darkMode: Boolean,
@@ -95,15 +90,8 @@ fun ThemePreferenceCategory(
}
}
Row(
modifier = Modifier
.fillMaxWidth()
.horizontalScroll(rememberScrollState())
.padding(16.dp)
) {
colorPreferences()
}
HorizontalDivider()
colorPreferences()
HorizontalDivider(modifier = Modifier.padding(top = 8.dp))
}
}
@@ -1,20 +1,16 @@
package de.mm20.launcher2.ui.settings.colorscheme
import androidx.compose.foundation.clickable
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Add
import androidx.compose.material.icons.rounded.Edit
import androidx.compose.material.icons.rounded.Lock
import androidx.compose.material.icons.rounded.OpenInNew
@@ -60,6 +56,7 @@ import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.badges.Badge
import de.mm20.launcher2.icons.ColorLayer
import de.mm20.launcher2.icons.StaticLauncherIcon
import de.mm20.launcher2.icons.TintedIconLayer
@@ -78,8 +75,8 @@ import palettes.CorePalette
import java.util.UUID
@Composable
fun ThemeSettingsScreen(themeId: UUID) {
val viewModel: ThemesSettingsScreenVM = viewModel()
fun ColorSchemeSettingsScreen(themeId: UUID) {
val viewModel: ColorSchemesSettingsScreenVM = viewModel()
val context = LocalContext.current
val dark = LocalDarkTheme.current
@@ -107,7 +104,13 @@ fun ThemeSettingsScreen(themeId: UUID) {
var name by remember(theme) { mutableStateOf(theme?.name ?: "") }
AlertDialog(
onDismissRequest = { editName = false },
text = { OutlinedTextField(value = name, onValueChange = { name = it }, singleLine = true) },
text = {
OutlinedTextField(
value = name,
onValueChange = { name = it },
singleLine = true
)
},
confirmButton = {
Button(
onClick = {
@@ -146,135 +149,129 @@ fun ThemeSettingsScreen(themeId: UUID) {
stringResource(R.string.preference_custom_colors_corepalette),
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.secondary,
modifier = Modifier.padding(horizontal = 16.dp),
modifier = Modifier.padding(bottom = 16.dp, start = 16.dp, end = 16.dp),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Row(
modifier = Modifier
.horizontalScroll(rememberScrollState())
.padding(16.dp)
) {
CorePaletteColorPreference(
title = "Primary",
value = theme?.corePalette?.primary,
onValueChange = {
viewModel.updateTheme(
theme!!.copy(
corePalette = theme!!.corePalette.copy(
primary = it
)
CorePaletteColorPreference(
title = "Primary",
value = theme?.corePalette?.primary,
onValueChange = {
viewModel.updateTheme(
theme!!.copy(
corePalette = theme!!.corePalette.copy(
primary = it
)
)
},
defaultValue = systemPalette.primary,
modifier = Modifier.padding(end = 12.dp),
)
CorePaletteColorPreference(
title = "Secondary",
value = theme?.corePalette?.secondary,
onValueChange = {
viewModel.updateTheme(
theme!!.copy(
corePalette = theme!!.corePalette.copy(
secondary = it
)
)
},
defaultValue = systemPalette.primary,
modifier = Modifier.padding(end = 12.dp),
)
CorePaletteColorPreference(
title = "Secondary",
value = theme?.corePalette?.secondary,
onValueChange = {
viewModel.updateTheme(
theme!!.copy(
corePalette = theme!!.corePalette.copy(
secondary = it
)
)
},
defaultValue = systemPalette.secondary,
modifier = Modifier.padding(end = 12.dp),
autoGenerate = {
theme!!.corePalette.primary?.let {
CorePalette.of(it).a2.keyColor.toInt()
}
},
)
CorePaletteColorPreference(
title = "Tertiary",
value = theme?.corePalette?.tertiary,
onValueChange = {
viewModel.updateTheme(
theme!!.copy(
corePalette = theme!!.corePalette.copy(
tertiary = it
)
)
},
defaultValue = systemPalette.secondary,
modifier = Modifier.padding(end = 12.dp),
autoGenerate = {
theme!!.corePalette.primary?.let {
CorePalette.of(it).a2.keyColor.toInt()
}
},
)
CorePaletteColorPreference(
title = "Tertiary",
value = theme?.corePalette?.tertiary,
onValueChange = {
viewModel.updateTheme(
theme!!.copy(
corePalette = theme!!.corePalette.copy(
tertiary = it
)
)
},
defaultValue = systemPalette.tertiary,
modifier = Modifier.padding(end = 12.dp),
autoGenerate = {
theme!!.corePalette.primary?.let {
CorePalette.of(it).a3.keyColor.toInt()
}
},
)
CorePaletteColorPreference(
title = "Neutral",
value = theme?.corePalette?.neutral,
onValueChange = {
viewModel.updateTheme(
theme!!.copy(
corePalette = theme!!.corePalette.copy(
neutral = it
)
)
},
defaultValue = systemPalette.tertiary,
modifier = Modifier.padding(end = 12.dp),
autoGenerate = {
theme!!.corePalette.primary?.let {
CorePalette.of(it).a3.keyColor.toInt()
}
},
)
CorePaletteColorPreference(
title = "Neutral",
value = theme?.corePalette?.neutral,
onValueChange = {
viewModel.updateTheme(
theme!!.copy(
corePalette = theme!!.corePalette.copy(
neutral = it
)
)
},
defaultValue = systemPalette.neutral,
modifier = Modifier.padding(end = 12.dp),
autoGenerate = {
theme!!.corePalette.primary?.let {
CorePalette.of(it).n1.keyColor.toInt()
}
},
)
CorePaletteColorPreference(
title = "Neutral Variant",
value = theme?.corePalette?.neutralVariant,
onValueChange = {
viewModel.updateTheme(
theme!!.copy(
corePalette = theme!!.corePalette.copy(
neutralVariant = it
)
)
},
defaultValue = systemPalette.neutral,
modifier = Modifier.padding(end = 12.dp),
autoGenerate = {
theme!!.corePalette.primary?.let {
CorePalette.of(it).n1.keyColor.toInt()
}
},
)
CorePaletteColorPreference(
title = "Neutral Variant",
value = theme?.corePalette?.neutralVariant,
onValueChange = {
viewModel.updateTheme(
theme!!.copy(
corePalette = theme!!.corePalette.copy(
neutralVariant = it
)
)
},
defaultValue = systemPalette.neutralVariant,
modifier = Modifier.padding(end = 12.dp),
autoGenerate = {
theme!!.corePalette.primary?.let {
CorePalette.of(it).n2.keyColor.toInt()
}
},
)
CorePaletteColorPreference(
title = "Error",
value = theme?.corePalette?.error,
onValueChange = {
viewModel.updateTheme(
theme!!.copy(
corePalette = theme!!.corePalette.copy(
error = it
)
)
},
defaultValue = systemPalette.neutralVariant,
modifier = Modifier.padding(end = 12.dp),
autoGenerate = {
theme!!.corePalette.primary?.let {
CorePalette.of(it).n2.keyColor.toInt()
}
},
)
CorePaletteColorPreference(
title = "Error",
value = theme?.corePalette?.error,
onValueChange = {
viewModel.updateTheme(
theme!!.copy(
corePalette = theme!!.corePalette.copy(
error = it
)
)
},
defaultValue = systemPalette.error,
autoGenerate = {
theme!!.corePalette.primary?.let {
CorePalette.of(it).error.keyColor.toInt()
}
},
)
}
HorizontalDivider()
)
},
defaultValue = systemPalette.error,
autoGenerate = {
theme!!.corePalette.primary?.let {
CorePalette.of(it).error.keyColor.toInt()
}
},
)
HorizontalDivider(modifier = Modifier.padding(top = 8.dp))
}
}
item {
ThemePreferenceCategory(
ColorSchemePreferenceCategory(
title = "Primary colors",
previewColorScheme = previewColorScheme,
darkMode = previewDarkTheme,
@@ -302,7 +299,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
)
},
defaultValue = selectedDefaultScheme.primary,
modifier = Modifier.padding(end = 12.dp),
)
ThemeColorPreference(
title = "On Primary",
@@ -326,7 +322,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
)
},
defaultValue = selectedDefaultScheme.onPrimary,
modifier = Modifier.padding(end = 12.dp),
)
ThemeColorPreference(
title = "Primary Container",
@@ -350,7 +345,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
)
},
defaultValue = selectedDefaultScheme.primaryContainer,
modifier = Modifier.padding(end = 12.dp),
)
ThemeColorPreference(
title = "On Primary Container",
@@ -374,7 +368,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
)
},
defaultValue = selectedDefaultScheme.onPrimaryContainer,
modifier = Modifier.padding(end = 12.dp),
)
},
@@ -419,7 +412,7 @@ fun ThemeSettingsScreen(themeId: UUID) {
}
}
item {
ThemePreferenceCategory(
ColorSchemePreferenceCategory(
title = "Secondary colors",
previewColorScheme = previewColorScheme,
darkMode = previewDarkTheme,
@@ -447,7 +440,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
)
},
defaultValue = selectedDefaultScheme.secondary,
modifier = Modifier.padding(end = 12.dp),
)
ThemeColorPreference(
title = "On Secondary",
@@ -471,7 +463,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
)
},
defaultValue = selectedDefaultScheme.onSecondary,
modifier = Modifier.padding(end = 12.dp),
)
ThemeColorPreference(
title = "Secondary Container",
@@ -495,7 +486,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
)
},
defaultValue = selectedDefaultScheme.secondaryContainer,
modifier = Modifier.padding(end = 12.dp),
)
ThemeColorPreference(
title = "On Secondary Container",
@@ -519,7 +509,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
)
},
defaultValue = selectedDefaultScheme.onSecondaryContainer,
modifier = Modifier.padding(end = 12.dp),
)
},
) {
@@ -557,7 +546,7 @@ fun ThemeSettingsScreen(themeId: UUID) {
}
}
item {
ThemePreferenceCategory(
ColorSchemePreferenceCategory(
title = "Tertiary colors",
previewColorScheme = previewColorScheme,
darkMode = previewDarkTheme,
@@ -585,7 +574,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
)
},
defaultValue = selectedDefaultScheme.tertiary,
modifier = Modifier.padding(end = 12.dp),
)
ThemeColorPreference(
title = "On Tertiary",
@@ -609,7 +597,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
)
},
defaultValue = selectedDefaultScheme.onTertiary,
modifier = Modifier.padding(end = 12.dp),
)
ThemeColorPreference(
title = "Tertiary Container",
@@ -633,7 +620,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
)
},
defaultValue = selectedDefaultScheme.tertiaryContainer,
modifier = Modifier.padding(end = 12.dp),
)
ThemeColorPreference(
title = "On Tertiary Container",
@@ -657,14 +643,17 @@ fun ThemeSettingsScreen(themeId: UUID) {
)
},
defaultValue = selectedDefaultScheme.onTertiaryContainer,
modifier = Modifier.padding(end = 12.dp),
)
},
) {
ShapedLauncherIcon(
badge = { Badge() },
size = 48.dp,
)
}
}
item {
ThemePreferenceCategory(
ColorSchemePreferenceCategory(
title = "Surface colors",
previewColorScheme = previewColorScheme,
darkMode = previewDarkTheme,
@@ -716,7 +705,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
)
},
defaultValue = selectedDefaultScheme.surface,
modifier = Modifier.padding(end = 12.dp),
)
ThemeColorPreference(
title = "Surface Bright",
@@ -740,7 +728,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
)
},
defaultValue = selectedDefaultScheme.surfaceBright,
modifier = Modifier.padding(end = 12.dp),
)
ThemeColorPreference(
title = "Surface Tint",
@@ -764,7 +751,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
)
},
defaultValue = selectedDefaultScheme.surfaceTint,
modifier = Modifier.padding(end = 12.dp),
)
},
) {
@@ -824,7 +810,7 @@ fun ThemeSettingsScreen(themeId: UUID) {
}
}
item {
ThemePreferenceCategory(
ColorSchemePreferenceCategory(
title = "Surface container colors",
previewColorScheme = previewColorScheme,
darkMode = previewDarkTheme,
@@ -852,7 +838,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
)
},
defaultValue = selectedDefaultScheme.surfaceContainerLowest,
modifier = Modifier.padding(end = 12.dp),
)
ThemeColorPreference(
title = "Surface Container Low",
@@ -876,7 +861,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
)
},
defaultValue = selectedDefaultScheme.surfaceContainerLow,
modifier = Modifier.padding(end = 12.dp),
)
ThemeColorPreference(
title = "Surface Container",
@@ -900,7 +884,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
)
},
defaultValue = selectedDefaultScheme.surfaceContainer,
modifier = Modifier.padding(end = 12.dp),
)
ThemeColorPreference(
title = "Surface Container High",
@@ -924,7 +907,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
)
},
defaultValue = selectedDefaultScheme.surfaceContainerHigh,
modifier = Modifier.padding(end = 12.dp),
)
ThemeColorPreference(
title = "Surface Container Highest",
@@ -948,7 +930,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
)
},
defaultValue = selectedDefaultScheme.surfaceContainerHighest,
modifier = Modifier.padding(end = 12.dp),
)
ThemeColorPreference(
title = "Surface Variant",
@@ -972,7 +953,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
)
},
defaultValue = selectedDefaultScheme.surfaceVariant,
modifier = Modifier.padding(end = 12.dp),
)
},
) {
@@ -994,7 +974,7 @@ fun ThemeSettingsScreen(themeId: UUID) {
}
}
item {
ThemePreferenceCategory(
ColorSchemePreferenceCategory(
title = "Content colors",
previewColorScheme = previewColorScheme,
darkMode = previewDarkTheme,
@@ -1022,7 +1002,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
)
},
defaultValue = selectedDefaultScheme.onSurface,
modifier = Modifier.padding(end = 12.dp),
)
@@ -1048,7 +1027,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
)
},
defaultValue = selectedDefaultScheme.onSurfaceVariant,
modifier = Modifier.padding(end = 12.dp),
)
ThemeColorPreference(
@@ -1073,7 +1051,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
)
},
defaultValue = selectedDefaultScheme.onSurfaceVariant,
modifier = Modifier.padding(end = 12.dp),
)
},
) {
@@ -1126,7 +1103,7 @@ fun ThemeSettingsScreen(themeId: UUID) {
}
Surface(
color = MaterialTheme.colorScheme.surface,
color = MaterialTheme.colorScheme.surfaceContainer,
shape = MaterialTheme.shapes.extraSmall,
tonalElevation = 3.dp,
shadowElevation = 3.dp,
@@ -1148,7 +1125,7 @@ fun ThemeSettingsScreen(themeId: UUID) {
}
}
item {
ThemePreferenceCategory(
ColorSchemePreferenceCategory(
title = "Outline colors",
previewColorScheme = previewColorScheme,
darkMode = previewDarkTheme,
@@ -1176,7 +1153,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
)
},
defaultValue = selectedDefaultScheme.outline,
modifier = Modifier.padding(end = 12.dp),
)
ThemeColorPreference(
title = "Outline Variant",
@@ -1200,7 +1176,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
)
},
defaultValue = selectedDefaultScheme.outlineVariant,
modifier = Modifier.padding(end = 12.dp),
)
},
) {
@@ -1240,7 +1215,7 @@ fun ThemeSettingsScreen(themeId: UUID) {
}
}
item {
ThemePreferenceCategory(
ColorSchemePreferenceCategory(
title = "Error colors",
previewColorScheme = previewColorScheme,
darkMode = previewDarkTheme,
@@ -1268,7 +1243,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
)
},
defaultValue = selectedDefaultScheme.error,
modifier = Modifier.padding(end = 12.dp),
)
ThemeColorPreference(
title = "On Error",
@@ -1292,7 +1266,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
)
},
defaultValue = selectedDefaultScheme.onError,
modifier = Modifier.padding(end = 12.dp),
)
ThemeColorPreference(
title = "Error Container",
@@ -1316,7 +1289,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
)
},
defaultValue = selectedDefaultScheme.errorContainer,
modifier = Modifier.padding(end = 12.dp),
)
ThemeColorPreference(
title = "On Error Container",
@@ -1340,7 +1312,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
)
},
defaultValue = selectedDefaultScheme.onErrorContainer,
modifier = Modifier.padding(end = 12.dp),
)
},
) {
@@ -1354,7 +1325,7 @@ fun ThemeSettingsScreen(themeId: UUID) {
}
}
item {
ThemePreferenceCategory(
ColorSchemePreferenceCategory(
title = "Inverse colors",
previewColorScheme = previewColorScheme,
darkMode = previewDarkTheme,
@@ -1382,7 +1353,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
)
},
defaultValue = selectedDefaultScheme.inverseSurface,
modifier = Modifier.padding(end = 12.dp),
)
ThemeColorPreference(
title = "Inverse Surface",
@@ -1406,7 +1376,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
)
},
defaultValue = selectedDefaultScheme.inverseOnSurface,
modifier = Modifier.padding(end = 12.dp),
)
ThemeColorPreference(
title = "Inverse Primary",
@@ -1430,7 +1399,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
)
},
defaultValue = selectedDefaultScheme.inversePrimary,
modifier = Modifier.padding(end = 12.dp),
)
},
) {
@@ -45,7 +45,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.themes.Theme
import de.mm20.launcher2.themes.Colors
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.common.ImportThemeSheet
import de.mm20.launcher2.ui.component.preferences.Preference
@@ -57,15 +57,15 @@ import de.mm20.launcher2.ui.theme.colorscheme.darkColorSchemeOf
import de.mm20.launcher2.ui.theme.colorscheme.lightColorSchemeOf
@Composable
fun ThemesSettingsScreen() {
val viewModel: ThemesSettingsScreenVM = viewModel()
fun ColorSchemesSettingsScreen() {
val viewModel: ColorSchemesSettingsScreenVM = viewModel()
val navController = LocalNavController.current
val context = LocalContext.current
val selectedTheme by viewModel.selectedTheme.collectAsStateWithLifecycle(null)
val themes by viewModel.themes.collectAsStateWithLifecycle(emptyList())
val selectedTheme by viewModel.selectedColors.collectAsStateWithLifecycle(null)
val themes by viewModel.colors.collectAsStateWithLifecycle(emptyList())
var deleteTheme by remember { mutableStateOf<Theme?>(null) }
var deleteColors by remember { mutableStateOf<Colors?>(null) }
var importThemeUri by remember { mutableStateOf<Uri?>(null) }
@@ -114,7 +114,7 @@ fun ThemesSettingsScreen() {
},
text = { Text(stringResource(R.string.edit)) },
onClick = {
navController?.navigate("settings/appearance/themes/${theme.id}")
navController?.navigate("settings/appearance/colors/${theme.id}")
showMenu = false
}
)
@@ -146,7 +146,7 @@ fun ThemesSettingsScreen() {
},
text = { Text(stringResource(R.string.menu_delete)) },
onClick = {
deleteTheme = theme
deleteColors = theme
showMenu = false
}
)
@@ -162,22 +162,22 @@ fun ThemesSettingsScreen() {
}
}
}
if (deleteTheme != null) {
if (deleteColors != null) {
AlertDialog(
onDismissRequest = { deleteTheme = null },
onDismissRequest = { deleteColors = null },
text = {
Text(
stringResource(
R.string.confirmation_delete_color_scheme,
deleteTheme!!.name
deleteColors!!.name
)
)
},
confirmButton = {
TextButton(
onClick = {
viewModel.delete(deleteTheme!!)
deleteTheme = null
viewModel.delete(deleteColors!!)
deleteColors = null
}
) {
Text(stringResource(android.R.string.ok))
@@ -185,7 +185,7 @@ fun ThemesSettingsScreen() {
},
dismissButton = {
TextButton(
onClick = { deleteTheme = null }
onClick = { deleteColors = null }
) {
Text(stringResource(android.R.string.cancel))
}
@@ -199,9 +199,9 @@ fun ThemesSettingsScreen() {
}
@Composable
fun ColorSchemePreview(theme: Theme) {
fun ColorSchemePreview(colors: Colors) {
val dark = LocalDarkTheme.current
val scheme = if (dark) darkColorSchemeOf(theme) else lightColorSchemeOf(theme)
val scheme = if (dark) darkColorSchemeOf(colors) else lightColorSchemeOf(colors)
Box(
modifier = Modifier
.height(28.dp)
@@ -6,13 +6,13 @@ import androidx.core.content.FileProvider
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import de.mm20.launcher2.ktx.tryStartActivity
import de.mm20.launcher2.preferences.ThemeDescriptor
import de.mm20.launcher2.preferences.ColorsDescriptor
import de.mm20.launcher2.preferences.ui.UiSettings
import de.mm20.launcher2.themes.BlackAndWhiteThemeId
import de.mm20.launcher2.themes.DefaultThemeId
import de.mm20.launcher2.themes.Theme
import de.mm20.launcher2.themes.Colors
import de.mm20.launcher2.themes.ThemeRepository
import de.mm20.launcher2.themes.toJson
import de.mm20.launcher2.themes.toLegacyJson
import de.mm20.launcher2.ui.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
@@ -24,49 +24,49 @@ import org.koin.core.component.inject
import java.io.File
import java.util.UUID
class ThemesSettingsScreenVM : ViewModel(), KoinComponent {
class ColorSchemesSettingsScreenVM : ViewModel(), KoinComponent {
private val themeRepository: ThemeRepository by inject()
private val uiSettings: UiSettings by inject()
val selectedTheme = uiSettings.theme.map {
val selectedColors = uiSettings.colors.map {
when(it) {
ThemeDescriptor.Default -> DefaultThemeId
ThemeDescriptor.BlackAndWhite -> BlackAndWhiteThemeId
is ThemeDescriptor.Custom -> UUID.fromString(it.id)
ColorsDescriptor.Default -> DefaultThemeId
ColorsDescriptor.BlackAndWhite -> BlackAndWhiteThemeId
is ColorsDescriptor.Custom -> UUID.fromString(it.id)
}
}
val themes: Flow<List<Theme>> = themeRepository.getThemes()
val colors: Flow<List<Colors>> = themeRepository.getAllColors()
fun getTheme(id: UUID): Flow<Theme?> {
return themeRepository.getTheme(id)
fun getTheme(id: UUID): Flow<Colors?> {
return themeRepository.getColors(id)
}
fun updateTheme(theme: Theme) {
themeRepository.updateTheme(theme)
fun updateTheme(colors: Colors) {
themeRepository.updateColors(colors)
}
fun selectTheme(theme: Theme) {
uiSettings.setTheme(when(theme.id) {
DefaultThemeId -> ThemeDescriptor.Default
BlackAndWhiteThemeId -> ThemeDescriptor.BlackAndWhite
else -> ThemeDescriptor.Custom(theme.id.toString())
fun selectTheme(colors: Colors) {
uiSettings.setColors(when(colors.id) {
DefaultThemeId -> ColorsDescriptor.Default
BlackAndWhiteThemeId -> ColorsDescriptor.BlackAndWhite
else -> ColorsDescriptor.Custom(colors.id.toString())
})
}
fun duplicate(theme: Theme) {
themeRepository.createTheme(theme.copy(id = UUID.randomUUID()))
fun duplicate(colors: Colors) {
themeRepository.createColors(colors.copy(id = UUID.randomUUID()))
}
fun delete(theme: Theme) {
themeRepository.deleteTheme(theme)
fun delete(colors: Colors) {
themeRepository.deleteColors(colors)
}
fun exportTheme(context: Context, theme: Theme) {
fun exportTheme(context: Context, colors: Colors) {
viewModelScope.launch {
val file = withContext(Dispatchers.IO) {
val file = File(context.cacheDir, "${theme.name}.kvtheme")
file.writeText(theme.toJson())
val file = File(context.cacheDir, "${colors.name}.kvtheme")
file.writeText(colors.toLegacyJson())
file
}
context.tryStartActivity(Intent().apply {
@@ -82,8 +82,8 @@ class ThemesSettingsScreenVM : ViewModel(), KoinComponent {
}
fun createNew(context: Context) {
themeRepository.createTheme(
Theme(
themeRepository.createColors(
Colors(
id = UUID.randomUUID(),
name = context.getString(R.string.new_color_scheme_name)
)
@@ -3,8 +3,10 @@ package de.mm20.launcher2.ui.settings.colorscheme
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
@@ -16,6 +18,7 @@ import androidx.compose.material.icons.rounded.SettingsSuggest
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
@@ -28,7 +31,11 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import de.mm20.launcher2.themes.get
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.BottomSheetDialog
import de.mm20.launcher2.ui.component.Tooltip
@@ -47,19 +54,25 @@ fun CorePaletteColorPreference(
) {
var showDialog by remember { mutableStateOf(false) }
Tooltip(
tooltipText = title
Row(
modifier = modifier.fillMaxWidth()
.clickable(
onClick = { showDialog = true },
)
.padding(horizontal = 16.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
ColorSwatch(
color = Color(value ?: defaultValue),
modifier = modifier
.size(48.dp)
.combinedClickable(
onClick = { showDialog = true },
onLongClick = {
onValueChange(null)
}
),
modifier = Modifier.padding(end = 20.dp).size(48.dp),
)
Text(
title,
style = MaterialTheme.typography.titleMedium,
textAlign = TextAlign.Center,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
)
}
@@ -4,6 +4,7 @@ import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -11,6 +12,7 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
@@ -45,7 +47,9 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.Fill
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import de.mm20.launcher2.themes.ColorRef
import de.mm20.launcher2.themes.CorePaletteColor
@@ -66,7 +70,7 @@ import de.mm20.launcher2.themes.Color as ThemeColor
@Composable
fun ThemeColorPreference(
title: String,
value: de.mm20.launcher2.themes.Color?,
value: ThemeColor?,
corePalette: FullCorePalette,
onValueChange: (ThemeColor?) -> Unit,
defaultValue: ThemeColor,
@@ -74,16 +78,25 @@ fun ThemeColorPreference(
) {
var showDialog by remember { mutableStateOf(false) }
Tooltip(
tooltipText = title
Row(
modifier = modifier.fillMaxWidth()
.clickable(
onClick = { showDialog = true },
)
.padding(horizontal = 16.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
ColorSwatch(
color = Color((value ?: defaultValue).get(corePalette)),
modifier = modifier
.size(48.dp)
.clickable(
onClick = { showDialog = true },
),
modifier = Modifier.padding(end = 20.dp).size(48.dp),
)
Text(
title,
style = MaterialTheme.typography.bodyMedium.copy(fontFamily = FontFamily.Monospace),
textAlign = TextAlign.Center,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
)
}
@@ -0,0 +1,733 @@
package de.mm20.launcher2.ui.settings.shapes
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
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.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CutCornerShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Edit
import androidx.compose.material.icons.rounded.OpenInNew
import androidx.compose.material.icons.rounded.RestartAlt
import androidx.compose.material.icons.rounded.RoundedCorner
import androidx.compose.material.icons.rounded.Search
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.BottomSheetDefaults
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.FilterChip
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.SegmentedButton
import androidx.compose.material3.SegmentedButtonDefaults
import androidx.compose.material3.Shapes
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
import androidx.compose.material3.Slider
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.draw.rotate
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.min
import androidx.compose.ui.unit.times
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.icons.CutCorner
import de.mm20.launcher2.icons.RoundedCornerAlt
import de.mm20.launcher2.themes.CornerStyle
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.BottomSheetDialog
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
import de.mm20.launcher2.ui.ktx.withCorners
import de.mm20.launcher2.ui.theme.shapes.shapesOf
import java.util.UUID
import kotlin.math.max
import kotlin.math.min
import de.mm20.launcher2.themes.Shape as ThemeShape
@Composable
fun ShapeSchemeSettingsScreen(themeId: UUID) {
val viewModel: ShapeSchemesSettingsScreenVM = viewModel()
val context = LocalContext.current
val theme by remember(
viewModel,
themeId
) { viewModel.getShapes(themeId) }.collectAsStateWithLifecycle(null)
val previewShapes = theme?.let { shapesOf(it) }
var editName by remember { mutableStateOf(false) }
if (editName) {
var name by remember(theme) { mutableStateOf(theme?.name ?: "") }
AlertDialog(
onDismissRequest = { editName = false },
text = {
OutlinedTextField(
value = name,
onValueChange = { name = it },
singleLine = true
)
},
confirmButton = {
Button(
onClick = {
viewModel.updateShapes(theme!!.copy(name = name))
editName = false
}
) {
Text(stringResource(R.string.save))
}
}
)
}
PreferenceScreen(
title = {
Text(
theme?.name ?: "",
modifier = Modifier.clickable {
editName = true
},
)
},
helpUrl = "https://kvaesitso.mm20.de/docs/user-guide/customization/color-schemes",
) {
if (theme == null || previewShapes == null) return@PreferenceScreen
val baseShape = theme!!.baseShape
item {
PreferenceCategory {
ShapePreference(
title = stringResource(R.string.preference_shapes_base),
shape = baseShape,
baseShape = baseShape,
factor = 1f,
onValueChange = {
viewModel.updateShapes(
theme!!.copy(
baseShape = it ?: ThemeShape(
corners = CornerStyle.Rounded,
radii = intArrayOf(12, 12, 12, 12)
)
)
)
},
titleTextStyle = MaterialTheme.typography.titleMedium
)
}
}
item {
PreferenceCategory {
ShapePreview(
previewShapes = previewShapes,
) {
Column(
modifier = Modifier
.width(200.dp)
) {
Box(
Modifier
.fillMaxWidth()
.height(32.dp)
.background(
MaterialTheme.colorScheme.surface,
MaterialTheme.shapes.extraSmall.withCorners(
topStart = false,
topEnd = false
)
)
)
Box(
Modifier
.fillMaxWidth()
.padding(top = 2.dp)
.height(32.dp)
.background(
MaterialTheme.colorScheme.surface,
MaterialTheme.shapes.extraSmall.withCorners(
bottomStart = false,
bottomEnd = false
)
)
)
}
Surface(
color = MaterialTheme.colorScheme.surfaceContainer,
shape = MaterialTheme.shapes.extraSmall,
tonalElevation = 3.dp,
shadowElevation = 3.dp,
modifier = Modifier.wrapContentWidth()
) {
Column(
modifier = Modifier
.padding(vertical = 8.dp)
.width(IntrinsicSize.Max),
) {
DropdownMenuItem(
leadingIcon = {
Icon(Icons.Rounded.OpenInNew, null)
},
text = { Text("Menu") },
onClick = { })
}
}
}
ShapePreference(
title = "Extra small",
shape = theme!!.extraSmall,
baseShape = baseShape,
factor = 1f / 3f,
onValueChange = {
viewModel.updateShapes(
theme!!.copy(extraSmall = it)
)
}
)
}
PreferenceCategory {
ShapePreview(
previewShapes = previewShapes,
) {
FilterChip(
onClick = {},
label = {
Text("Chip")
},
selected = false,
)
}
ShapePreference(
title = "Small",
shape = theme!!.small,
baseShape = baseShape,
factor = 2f / 3f,
onValueChange = {
viewModel.updateShapes(
theme!!.copy(small = it)
)
}
)
}
PreferenceCategory {
ShapePreview(
previewShapes = previewShapes,
) {
Box(
Modifier
.fillMaxWidth()
.width(200.dp)
.height(48.dp)
.background(
MaterialTheme.colorScheme.surface,
MaterialTheme.shapes.medium
)
) {
Icon(
Icons.Rounded.Search,
null,
modifier = Modifier.padding(12.dp)
)
}
}
ShapePreference(
title = "Medium",
shape = theme!!.medium,
baseShape = baseShape,
factor = 1f,
onValueChange = {
viewModel.updateShapes(
theme!!.copy(medium = it)
)
}
)
}
PreferenceCategory {
ShapePreview(
previewShapes = previewShapes,
) {
FloatingActionButton(onClick = {}) {
Icon(Icons.Rounded.Edit, null)
}
}
ShapePreference(
title = "Large",
shape = theme!!.large,
baseShape = baseShape,
factor = 4f / 3f,
onValueChange = {
viewModel.updateShapes(
theme!!.copy(large = it)
)
}
)
}
PreferenceCategory {
ShapePreference(
title = "Large increased",
shape = theme!!.largeIncreased,
baseShape = baseShape,
factor = 5f / 3f,
onValueChange = {
viewModel.updateShapes(
theme!!.copy(largeIncreased = it)
)
}
)
}
PreferenceCategory {
ShapePreview(
previewShapes = previewShapes,
) {
Surface(
shape = BottomSheetDefaults.ExpandedShape,
color = BottomSheetDefaults.ContainerColor,
shadowElevation = BottomSheetDefaults.Elevation,
tonalElevation = BottomSheetDefaults.Elevation,
modifier = Modifier
.width(250.dp)
.height(144.dp),
) {
Box(
contentAlignment = Alignment.TopCenter,
) {
BottomSheetDefaults.DragHandle()
}
}
}
ShapePreference(
title = "Extra large",
shape = theme!!.extraLarge,
baseShape = baseShape,
factor = 7f / 3f,
onValueChange = {
viewModel.updateShapes(
theme!!.copy(extraLarge = it)
)
}
)
}
PreferenceCategory {
ShapePreference(
title = "Extra large increased",
shape = theme!!.extraLargeIncreased,
baseShape = baseShape,
factor = 8f / 3f,
onValueChange = {
viewModel.updateShapes(
theme!!.copy(extraLargeIncreased = it)
)
}
)
}
PreferenceCategory {
ShapePreference(
title = "Extra extra large",
shape = theme!!.extraExtraLarge,
baseShape = baseShape,
factor = 12f / 3f,
onValueChange = {
viewModel.updateShapes(
theme!!.copy(extraExtraLarge = it)
)
}
)
}
}
}
}
@Composable
fun ShapePreference(
title: String,
shape: ThemeShape?,
baseShape: ThemeShape,
factor: Float = 1f,
onValueChange: (ThemeShape?) -> Unit,
titleTextStyle: TextStyle = MaterialTheme.typography.bodyMedium.copy(fontFamily = FontFamily.Monospace)
) {
var showDialog by remember { mutableStateOf(false) }
val f = min(1f, factor)
val topStart =
(shape?.radii?.get(0)?.div(factor) ?: baseShape.radii?.get(0)?.toFloat() ?: 12f) * f
val topEnd =
(shape?.radii?.get(1)?.div(factor) ?: baseShape.radii?.get(1)?.toFloat() ?: 12f) * f
val bottomEnd =
(shape?.radii?.get(2)?.div(factor) ?: baseShape.radii?.get(2)?.toFloat() ?: 12f) * f
val bottomStart =
(shape?.radii?.get(3)?.div(factor) ?: baseShape.radii?.get(3)?.toFloat() ?: 12f) * f
Row(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 8.dp)
.clickable(
onClick = { showDialog = true },
)
.padding(horizontal = 16.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Box(
modifier = Modifier
.padding(end = 20.dp)
.size(48.dp),
contentAlignment = Alignment.Center
) {
Box(
modifier = Modifier
.size(min(48.dp, factor * 48.dp))
.border(
2.dp,
MaterialTheme.colorScheme.primary,
if ((shape?.corners ?: baseShape.corners
?: CornerStyle.Rounded) == CornerStyle.Cut
) {
CutCornerShape(
topStart = topStart.dp,
topEnd = topEnd.dp,
bottomEnd = bottomEnd.dp,
bottomStart = bottomStart.dp
)
} else {
RoundedCornerShape(
topStart = topStart.dp,
topEnd = topEnd.dp,
bottomEnd = bottomEnd.dp,
bottomStart = bottomStart.dp
)
}
)
)
}
Text(
title,
style = titleTextStyle,
textAlign = TextAlign.Center,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
)
}
if (showDialog) {
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()
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)) }
val actualCornerStyle = currentCornerStyle ?: baseShape.corners ?: CornerStyle.Rounded
val actualTopStart = currentTopStart ?: baseTopStart
val actualTopEnd = currentTopEnd ?: baseTopEnd
val actualBottomEnd = currentBottomEnd ?: baseBottomEnd
val actualBottomStart = currentBottomStart ?: baseBottomStart
BottomSheetDialog(
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),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
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
)
} 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
)
)
SingleChoiceSegmentedButtonRow(
modifier = Modifier
.fillMaxWidth()
) {
SegmentedButton(
selected = actualCornerStyle == CornerStyle.Rounded,
onClick = {
currentCornerStyle = CornerStyle.Rounded
},
shape = SegmentedButtonDefaults.itemShape(0, 2),
icon = {
SegmentedButtonDefaults.Icon(
active = actualCornerStyle == CornerStyle.Rounded,
) {
Icon(Icons.Rounded.RoundedCornerAlt, null)
}
}
) {
Text(stringResource(R.string.preference_cards_shape_rounded))
}
SegmentedButton(
selected = actualCornerStyle == CornerStyle.Cut,
onClick = {
currentCornerStyle = CornerStyle.Cut
},
shape = SegmentedButtonDefaults.itemShape(1, 2),
icon = {
SegmentedButtonDefaults.Icon(
active = actualCornerStyle == CornerStyle.Cut,
) {
Icon(Icons.Rounded.CutCorner, null)
}
}
) {
Text(stringResource(R.string.preference_cards_shape_cut))
}
}
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
Icons.Rounded.RoundedCorner,
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(
Icons.Rounded.RoundedCorner,
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(
Icons.Rounded.RoundedCorner,
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(
Icons.Rounded.RoundedCorner,
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)
)
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(
Icons.Rounded.RestartAlt, null,
modifier = Modifier
.padding(ButtonDefaults.IconSpacing)
.size(ButtonDefaults.IconSize)
)
Text(stringResource(R.string.preference_restore_default))
}
}
}
}
}
@Composable
private fun ShapePreview(
previewShapes: Shapes,
content: @Composable () -> Unit,
) {
MaterialTheme(
shapes = previewShapes
) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceContainer
)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.horizontalScroll(rememberScrollState())
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
content()
}
}
}
}
@@ -0,0 +1,204 @@
package de.mm20.launcher2.ui.settings.shapes
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CutCornerShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Add
import androidx.compose.material.icons.rounded.ContentCopy
import androidx.compose.material.icons.rounded.Delete
import androidx.compose.material.icons.rounded.Edit
import androidx.compose.material.icons.rounded.MoreVert
import androidx.compose.material.icons.rounded.RadioButtonChecked
import androidx.compose.material.icons.rounded.RadioButtonUnchecked
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
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.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.themes.CornerStyle
import de.mm20.launcher2.themes.Shapes
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.preferences.Preference
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
import de.mm20.launcher2.ui.locals.LocalNavController
import de.mm20.launcher2.ui.theme.shapes.shapesOf
@Composable
fun ShapeSchemesSettingsScreen() {
val viewModel: ShapeSchemesSettingsScreenVM = viewModel()
val navController = LocalNavController.current
val context = LocalContext.current
val selectedTheme by viewModel.selectedShapes.collectAsStateWithLifecycle(null)
val themes by viewModel.shapes.collectAsStateWithLifecycle(emptyList())
var deleteShapes by remember { mutableStateOf<Shapes?>(null) }
PreferenceScreen(
title = stringResource(R.string.preference_screen_shapes),
floatingActionButton = {
FloatingActionButton(onClick = { viewModel.createNew(context) }) {
Icon(Icons.Rounded.Add, null)
}
}
) {
item {
PreferenceCategory {
for (theme in themes) {
var showMenu by remember { mutableStateOf(false) }
Preference(
icon = if (theme.id == selectedTheme) Icons.Rounded.RadioButtonChecked else Icons.Rounded.RadioButtonUnchecked,
title = theme.name,
controls = {
Row(
verticalAlignment = Alignment.CenterVertically,
) {
ShapesPreview(theme)
IconButton(
modifier = Modifier.padding(start = 12.dp),
onClick = { showMenu = true }) {
Icon(Icons.Rounded.MoreVert, null)
}
DropdownMenu(
expanded = showMenu,
onDismissRequest = { showMenu = false }
) {
if (!theme.builtIn) {
DropdownMenuItem(
leadingIcon = {
Icon(Icons.Rounded.Edit, null)
},
text = { Text(stringResource(R.string.edit)) },
onClick = {
navController?.navigate("settings/appearance/shapes/${theme.id}")
showMenu = false
}
)
}
DropdownMenuItem(
leadingIcon = {
Icon(Icons.Rounded.ContentCopy, null)
},
text = { Text(stringResource(R.string.duplicate)) },
onClick = {
viewModel.duplicate(theme)
showMenu = false
}
)
if (!theme.builtIn) {
DropdownMenuItem(
leadingIcon = {
Icon(Icons.Rounded.Delete, null)
},
text = { Text(stringResource(R.string.menu_delete)) },
onClick = {
deleteShapes = theme
showMenu = false
}
)
}
}
}
},
onClick = {
viewModel.selectShapes(theme)
}
)
}
}
}
}
if (deleteShapes != null) {
AlertDialog(
onDismissRequest = { deleteShapes = null },
text = {
Text(
stringResource(
R.string.confirmation_delete_shapes_scheme,
deleteShapes!!.name
)
)
},
confirmButton = {
TextButton(
onClick = {
viewModel.delete(deleteShapes!!)
deleteShapes = null
}
) {
Text(stringResource(android.R.string.ok))
}
},
dismissButton = {
TextButton(
onClick = { deleteShapes = null }
) {
Text(stringResource(android.R.string.cancel))
}
}
)
}
}
@Composable
private fun ShapesPreview(theme: Shapes) {
val shape = theme.medium
val baseShape = theme.baseShape
val topStart =
(shape?.radii?.get(0)?.toFloat() ?: baseShape.radii?.get(0)?.toFloat() ?: 8f) / 3f * 2f
val topEnd =
(shape?.radii?.get(1)?.toFloat() ?: baseShape.radii?.get(1)?.toFloat() ?: 8f) / 3f * 2f
val bottomEnd =
(shape?.radii?.get(2)?.toFloat() ?: baseShape.radii?.get(2)?.toFloat() ?: 8f) / 3f * 2f
val bottomStart =
(shape?.radii?.get(3)?.toFloat() ?: baseShape.radii?.get(3)?.toFloat() ?: 8f) / 3f * 2f
Box(
modifier = Modifier
.size(32.dp)
.border(
2.dp,
MaterialTheme.colorScheme.primary,
if ((theme.medium?.corners ?: theme.baseShape.corners
?: CornerStyle.Rounded) == CornerStyle.Cut
) {
CutCornerShape(
topStart = topStart.dp,
topEnd = topEnd.dp,
bottomEnd = bottomEnd.dp,
bottomStart = bottomStart.dp
)
} else {
RoundedCornerShape(
topStart = topStart.dp,
topEnd = topEnd.dp,
bottomEnd = bottomEnd.dp,
bottomStart = bottomStart.dp
)
}
)
)
}
@@ -0,0 +1,68 @@
package de.mm20.launcher2.ui.settings.shapes
import android.content.Context
import androidx.lifecycle.ViewModel
import de.mm20.launcher2.preferences.ShapesDescriptor
import de.mm20.launcher2.preferences.ui.UiSettings
import de.mm20.launcher2.themes.CutShapesId
import de.mm20.launcher2.themes.DefaultThemeId
import de.mm20.launcher2.themes.ExtraRoundShapesId
import de.mm20.launcher2.themes.RectShapesId
import de.mm20.launcher2.themes.Shapes
import de.mm20.launcher2.themes.ThemeRepository
import de.mm20.launcher2.ui.R
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import java.util.UUID
import kotlin.getValue
class ShapeSchemesSettingsScreenVM : ViewModel(), KoinComponent {
private val themeRepository: ThemeRepository by inject()
private val uiSettings: UiSettings by inject()
val selectedShapes = uiSettings.shapes.map {
when(it) {
ShapesDescriptor.Default -> DefaultThemeId
ShapesDescriptor.Cut -> CutShapesId
ShapesDescriptor.ExtraRound -> ExtraRoundShapesId
ShapesDescriptor.Rect -> RectShapesId
is ShapesDescriptor.Custom -> UUID.fromString(it.id)
}
}
val shapes: Flow<List<Shapes>> = themeRepository.getAllShapes()
fun getShapes(id: UUID): Flow<Shapes?> {
return themeRepository.getShapes(id)
}
fun updateShapes(shapes: Shapes) {
themeRepository.updateShapes(shapes)
}
fun selectShapes(shapes: Shapes) {
uiSettings.setShapes(when(shapes.id) {
DefaultThemeId -> ShapesDescriptor.Default
else -> ShapesDescriptor.Custom(shapes.id.toString())
})
}
fun duplicate(shapes: Shapes) {
themeRepository.createShapes(shapes.copy(id = UUID.randomUUID()))
}
fun delete(shapes: Shapes) {
themeRepository.deleteShapes(shapes)
}
fun createNew(context: Context) {
themeRepository.createShapes(
Shapes(
id = UUID.randomUUID(),
name = context.getString(R.string.new_shapes_name)
)
)
}
}
@@ -16,6 +16,7 @@ import de.mm20.launcher2.preferences.ui.UiSettings
import de.mm20.launcher2.themes.ThemeRepository
import de.mm20.launcher2.ui.locals.LocalDarkTheme
import de.mm20.launcher2.ui.theme.colorscheme.*
import de.mm20.launcher2.ui.theme.shapes.shapesOf
import de.mm20.launcher2.ui.theme.typography.DefaultTypography
import de.mm20.launcher2.ui.theme.typography.getDeviceDefaultTypography
import kotlinx.coroutines.flow.flatMapLatest
@@ -33,11 +34,17 @@ fun LauncherTheme(
val uiSettings: UiSettings = koinInject()
val themeRepository: ThemeRepository = koinInject()
val theme by remember {
uiSettings.theme.flatMapLatest {
themeRepository.getThemeOrDefault(it)
val themeColors by remember {
uiSettings.colors.flatMapLatest {
themeRepository.getColorsOrDefault(it)
}
}.collectAsState(themeRepository.getDefaultTheme())
}.collectAsState(null)
val themeShapes by remember {
uiSettings.shapes.flatMapLatest {
themeRepository.getShapesOrDefault(it)
}
}.collectAsState(null)
val colorSchemePref by remember { uiSettings.colorScheme }.collectAsState(
ColorSchemePref.System
@@ -45,27 +52,20 @@ fun LauncherTheme(
val darkTheme =
colorSchemePref == ColorSchemePref.Dark || colorSchemePref == ColorSchemePref.System && isSystemInDarkTheme()
val cornerRadius by remember {
uiSettings.cardStyle.map {
it.cornerRadius.dp
}
}.collectAsState(8.dp)
val baseShape by remember {
uiSettings.cardStyle.map {
when (it.shape) {
SurfaceShape.Cut -> CutCornerShape(0f)
else -> RoundedCornerShape(0f)
}
}
}.collectAsState(RoundedCornerShape(0f))
if (themeColors == null || themeShapes == null) {
return
}
val colorScheme = if (darkTheme) {
darkColorSchemeOf(theme)
darkColorSchemeOf(themeColors!!)
} else {
lightColorSchemeOf(theme)
lightColorSchemeOf(themeColors!!)
}
val shapes = shapesOf(themeShapes!!)
val font by remember { uiSettings.font }.collectAsState(
Font.Outfit
)
@@ -80,13 +80,7 @@ fun LauncherTheme(
MaterialExpressiveTheme(
colorScheme = colorScheme,
typography = typography,
shapes = Shapes(
extraSmall = baseShape.copy(CornerSize(cornerRadius / 3f)),
small = baseShape.copy(CornerSize(cornerRadius / 3f * 2f)),
medium = baseShape.copy(CornerSize(cornerRadius)),
large = baseShape.copy(CornerSize((cornerRadius / 3f * 4f).coerceAtMost(16.dp))),
extraLarge = baseShape.copy(CornerSize((cornerRadius / 3f * 7f).coerceAtMost(28.dp))),
),
shapes = shapes,
content = content
)
}
@@ -1,83 +0,0 @@
package de.mm20.launcher2.ui.theme.colorscheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.ui.graphics.Color
val LightBlackAndWhiteColorScheme = lightColorScheme(
primary = Color.Black,
onPrimary = Color.White,
primaryContainer = Color.White,
onPrimaryContainer = Color.Black,
inversePrimary = Color.White,
secondary = Color.Black,
onSecondary = Color.White,
secondaryContainer = Color.White,
onSecondaryContainer = Color.Black,
tertiary = Color.Black,
onTertiary = Color.White,
tertiaryContainer = Color.White,
onTertiaryContainer = Color.Black,
background = Color.White,
onBackground = Color.Black,
surface = Color.White,
onSurface = Color.Black,
surfaceVariant = Color.White,
onSurfaceVariant = Color.Black,
inverseSurface = Color.Black,
inverseOnSurface = Color.White,
error = Color(0xFFC10000),
onError = Color(0xFFFFFFFF),
errorContainer = Color(0xFFFFDAD3),
onErrorContainer = Color(0xFF410000),
outline = Color.Black,
surfaceTint = Color.White,
outlineVariant = Color.Black,
scrim = Color.Black,
surfaceDim = Color.White,
surfaceBright = Color.White,
surfaceContainer = Color.White,
surfaceContainerHigh = Color.White,
surfaceContainerHighest = Color.White,
surfaceContainerLow = Color.White,
surfaceContainerLowest = Color.White,
)
val DarkBlackAndWhiteColorScheme = darkColorScheme(
primary = Color.White,
onPrimary = Color.Black,
primaryContainer = Color.Black,
onPrimaryContainer = Color.White,
inversePrimary = Color.Black,
secondary = Color.White,
onSecondary = Color.Black,
secondaryContainer = Color.Black,
onSecondaryContainer = Color.White,
tertiary = Color.White,
onTertiary = Color.Black,
tertiaryContainer = Color.Black,
onTertiaryContainer = Color.White,
background = Color.Black,
onBackground = Color.White,
surface = Color.Black,
onSurface = Color.White,
surfaceVariant = Color.Black,
onSurfaceVariant = Color.White,
inverseSurface = Color.White,
inverseOnSurface = Color.Black,
error = Color(0xffffb4a6),
onError = Color(0xff690000),
errorContainer = Color(0xff940000),
onErrorContainer = Color(0xffffb4a6),
outline = Color.White,
surfaceTint = Color.White,
outlineVariant = Color.White,
scrim = Color.White,
surfaceDim = Color.Black,
surfaceBright = Color.Black,
surfaceContainer = Color.Black,
surfaceContainerHigh = Color.Black,
surfaceContainerHighest = Color.Black,
surfaceContainerLow = Color.Black,
surfaceContainerLowest = Color.Black,
)
@@ -16,20 +16,20 @@ import de.mm20.launcher2.themes.DefaultDarkColorScheme
import de.mm20.launcher2.themes.DefaultLightColorScheme
import de.mm20.launcher2.themes.FullColorScheme
import de.mm20.launcher2.themes.PartialCorePalette
import de.mm20.launcher2.themes.Theme
import de.mm20.launcher2.themes.Colors as ThemeColors
import de.mm20.launcher2.themes.get
import de.mm20.launcher2.themes.merge
import de.mm20.launcher2.ui.locals.LocalWallpaperColors
import org.koin.compose.koinInject
@Composable
fun lightColorSchemeOf(theme: Theme): ColorScheme {
return colorSchemeOf(theme.lightColorScheme.merge(DefaultLightColorScheme), theme.corePalette)
fun lightColorSchemeOf(colors: ThemeColors): ColorScheme {
return colorSchemeOf(colors.lightColorScheme.merge(DefaultLightColorScheme), colors.corePalette)
}
@Composable
fun darkColorSchemeOf(theme: Theme): ColorScheme {
return colorSchemeOf(theme.darkColorScheme.merge(DefaultDarkColorScheme), theme.corePalette)
fun darkColorSchemeOf(colors: ThemeColors): ColorScheme {
return colorSchemeOf(colors.darkColorScheme.merge(DefaultDarkColorScheme), colors.corePalette)
}
@Composable
@@ -1,67 +0,0 @@
package de.mm20.launcher2.ui.theme.colorscheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.ui.graphics.Color
val LightDefaultColorScheme = lightColorScheme(
primary = Color(0xFF3D608A),
onPrimary = Color(0xFFFFFFFF),
primaryContainer = Color(0xFFD2E4FF),
onPrimaryContainer = Color(0xFF001C37),
inversePrimary = Color(0xFFA5C9F8),
secondary = Color(0xFF535F70),
onSecondary = Color(0xFFFFFFFF),
secondaryContainer = Color(0xFFD7E3F8),
onSecondaryContainer = Color(0xFF101C2B),
tertiary = Color(0xFF6B5778),
onTertiary = Color(0xFFFFFFFF),
tertiaryContainer = Color(0xFFF3DAFF),
onTertiaryContainer = Color(0xFF251431),
surface = Color(0xFFFAF9FC),
onSurface = Color(0xFF1A1C1E),
onSurfaceVariant = Color(0xFF43474E),
inverseSurface = Color(0xFF2F3033),
inverseOnSurface = Color(0xFFF1F0F3),
error = Color(0xFFBA1A1A),
onError = Color(0xFFFFFFFF),
errorContainer = Color(0xFFFFDAD5),
onErrorContainer = Color(0xFF410002),
outline = Color(0xFF73777F),
outlineVariant = Color(0xFFC3C6CF),
scrim = Color(0xFF000000),
background = Color(0xFFFDFCFF),
onBackground = Color(0xFF1A1C1E),
surfaceVariant = Color(0xFFDFE2EB),
)
val DarkDefaultColorScheme = darkColorScheme(
primary = Color(0xFFA5C9F8),
onPrimary = Color(0xFF023258),
primaryContainer = Color(0xFF224970),
onPrimaryContainer = Color(0xFFD2E4FF),
inversePrimary = Color(0xFF3D608A),
secondary = Color(0xFFBBC7DB),
onSecondary = Color(0xFF253141),
secondaryContainer = Color(0xFF3C4858),
onSecondaryContainer = Color(0xFFD7E3F8),
tertiary = Color(0xFFD6BEE4),
onTertiary = Color(0xFF3B2947),
tertiaryContainer = Color(0xFF523F5F),
onTertiaryContainer = Color(0xFFF3DAFF),
surface = Color(0xFF1A1C1E),
onSurface = Color(0xFFE3E2E5),
onSurfaceVariant = Color(0xFFC3C6CF),
inverseSurface = Color(0xFFE3E2E5),
inverseOnSurface = Color(0xFF2F3033),
error = Color(0xFFFFB4AB),
onError = Color(0xFF690004),
errorContainer = Color(0xFF930009),
onErrorContainer = Color(0xFFFFB4AB),
outline = Color(0xFF8D9199),
outlineVariant = Color(0xFF43474E),
scrim = Color(0xFF000000),
background = Color(0xFF1A1C1E),
onBackground = Color(0xFFE3E2E5),
surfaceVariant = Color(0xFF43474E),
)
@@ -1,67 +0,0 @@
package de.mm20.launcher2.ui.theme.colorscheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.ui.graphics.Color
val LightEasterEggColorScheme = lightColorScheme(
primary = Color(0xFFB40180),
onPrimary = Color(0xFFFFFFFF),
primaryContainer = Color(0xFFFFD8E9),
onPrimaryContainer = Color(0xFF3C0029),
inversePrimary = Color(0xFFFFAFD7),
secondary = Color(0xFF725763),
onSecondary = Color(0xFFFFFFFF),
secondaryContainer = Color(0xFFFDD9E7),
onSecondaryContainer = Color(0xFF29151F),
tertiary = Color(0xFF7F543B),
onTertiary = Color(0xFFFFFFFF),
tertiaryContainer = Color(0xFFFFDBC9),
onTertiaryContainer = Color(0xFF311302),
surface = Color(0xFFFCF8FC),
onSurface = Color(0xFF1C1B1E),
onSurfaceVariant = Color(0xFF4F4448),
inverseSurface = Color(0xFF313033),
inverseOnSurface = Color(0xFFF3EFF3),
error = Color(0xFFBA1A1A),
onError = Color(0xFFFFFFFF),
errorContainer = Color(0xFFFFDAD5),
onErrorContainer = Color(0xFF410002),
outline = Color(0xFF817379),
outlineVariant = Color(0xFFD3C2C8),
scrim = Color(0xFF000000),
background = Color(0xFFFFFBFF),
onBackground = Color(0xFF1C1B1E),
surfaceVariant = Color(0xFFF0DEE4),
)
val DarkEasterEggColorScheme = darkColorScheme(
primary = Color(0xFFFFAFD7),
onPrimary = Color(0xFF620044),
primaryContainer = Color(0xFF8A0061),
onPrimaryContainer = Color(0xFFFFD8E9),
inversePrimary = Color(0xFFB40180),
secondary = Color(0xFFE0BDCB),
onSecondary = Color(0xFF402A35),
secondaryContainer = Color(0xFF59404B),
onSecondaryContainer = Color(0xFFFDD9E7),
tertiary = Color(0xFFF3BA9B),
onTertiary = Color(0xFF4A2812),
tertiaryContainer = Color(0xFF643D26),
onTertiaryContainer = Color(0xFFFFDBC9),
surface = Color(0xFF1C1B1E),
onSurface = Color(0xFFE5E1E5),
onSurfaceVariant = Color(0xFFD3C2C8),
inverseSurface = Color(0xFFE5E1E5),
inverseOnSurface = Color(0xFF313033),
error = Color(0xFFFFB4AB),
onError = Color(0xFF690004),
errorContainer = Color(0xFF930009),
onErrorContainer = Color(0xFFFFB4AB),
outline = Color(0xFF9C8D92),
outlineVariant = Color(0xFF4F4448),
scrim = Color(0xFF000000),
background = Color(0xFF1C1B1E),
onBackground = Color(0xFFE5E1E5),
surfaceVariant = Color(0xFF4F4448),
)
@@ -1,56 +0,0 @@
package de.mm20.launcher2.ui.theme.colorscheme
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import de.mm20.launcher2.ui.theme.WallpaperColors
import palettes.TonalPalette
import scheme.Scheme
fun MaterialYouCompatScheme(wallpaperColors: WallpaperColors, darkTheme: Boolean): ColorScheme {
val scheme = if (darkTheme) {
Scheme.dark(wallpaperColors.primary.toArgb())
} else {
Scheme.light(wallpaperColors.primary.toArgb())
}
return ColorScheme(
primary = Color(scheme.primary),
onPrimary = Color(scheme.onPrimary),
primaryContainer = Color(scheme.primaryContainer),
onPrimaryContainer = Color(scheme.onPrimaryContainer),
secondary = Color(scheme.secondary),
onSecondary = Color(scheme.onSecondary),
secondaryContainer = Color(scheme.secondaryContainer),
onSecondaryContainer = Color(scheme.onSecondaryContainer),
tertiary = Color(scheme.tertiary),
onTertiary = Color(scheme.onTertiary),
tertiaryContainer = Color(scheme.tertiaryContainer),
onTertiaryContainer = Color(scheme.onTertiaryContainer),
background = Color(scheme.background),
onBackground = Color(scheme.onBackground),
surface = Color(scheme.surface),
onSurface = Color(scheme.onSurface),
surfaceVariant = Color(scheme.surfaceVariant),
onSurfaceVariant = Color(scheme.onSurfaceVariant),
outline = Color(scheme.outline),
inverseSurface = Color(scheme.inverseSurface),
inverseOnSurface = Color(scheme.inverseOnSurface),
inversePrimary = Color(scheme.inversePrimary),
surfaceTint = Color(scheme.primary),
error = Color(scheme.error),
onError = Color(scheme.onError),
errorContainer = Color(scheme.errorContainer),
onErrorContainer = Color(scheme.onErrorContainer),
scrim = Color(scheme.scrim),
outlineVariant = Color(scheme.outlineVariant),
surfaceBright = Color(scheme.surfaceBright),
surfaceContainer = Color(scheme.surfaceContainer),
surfaceContainerHigh = Color(scheme.surfaceContainerHigh),
surfaceContainerHighest = Color(scheme.surfaceContainerHighest),
surfaceContainerLow = Color(scheme.surfaceContainerLow),
surfaceContainerLowest = Color(scheme.surfaceContainerLowest),
surfaceDim = Color(scheme.surfaceDim),
)
}
@@ -0,0 +1,64 @@
package de.mm20.launcher2.ui.theme.shapes
import androidx.compose.foundation.shape.CornerBasedShape
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.CutCornerShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Shapes
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.unit.dp
import de.mm20.launcher2.themes.CornerStyle
import de.mm20.launcher2.themes.Shape as ThemeShape
import de.mm20.launcher2.themes.Shapes as ThemeShapes
@Composable
fun shapesOf(shapes: ThemeShapes): Shapes {
return remember(shapes) {
Shapes(
extraSmall = fromShape(shapes.extraSmall, shapes.baseShape, 1f / 3f),
small = fromShape(shapes.small, shapes.baseShape, 2f / 3f),
medium = fromShape(shapes.medium, shapes.baseShape, 1f),
large = fromShape(shapes.large, shapes.baseShape, 4f / 3f),
largeIncreased = fromShape(shapes.largeIncreased, shapes.baseShape, 5f / 3f),
extraLarge = fromShape(shapes.extraLarge, shapes.baseShape, 7f / 3f),
extraLargeIncreased = fromShape(shapes.extraLargeIncreased, shapes.baseShape, 8f / 3f),
extraExtraLarge = fromShape(shapes.extraExtraLarge, shapes.baseShape, 12f / 3f),
)
}
}
private fun fromShape(shape: ThemeShape?, baseShape: ThemeShape, factor: Float): CornerBasedShape {
val topStart = getCornerRadius(shape, baseShape, factor, 0)
val topEnd = getCornerRadius(shape, baseShape, factor, 1)
val bottomEnd = getCornerRadius(shape, baseShape, factor, 2)
val bottomStart = getCornerRadius(shape, baseShape, factor, 3)
return if ((shape?.corners ?: baseShape.corners) == CornerStyle.Cut) {
CutCornerShape(
topStart = topStart,
topEnd = topEnd,
bottomEnd = bottomEnd,
bottomStart = bottomStart
)
} else {
RoundedCornerShape(
topStart = topStart,
topEnd = topEnd,
bottomEnd = bottomEnd,
bottomStart = bottomStart
)
}
}
private fun getCornerRadius(
shape: ThemeShape?,
baseShape: ThemeShape,
factor: Float,
index: Int
): CornerSize {
return CornerSize(
(shape?.radii?.get(index)?.toFloat() ?: ((baseShape.radii?.get(index)?.toFloat()
?: 12f) * factor)).dp
)
}