(feat) shape schemes
This commit is contained in:
parent
17c4a51ca5
commit
cfb7bb0c72
1
.idea/inspectionProfiles/Project_Default.xml
generated
1
.idea/inspectionProfiles/Project_Default.xml
generated
@ -67,6 +67,7 @@
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewParameterProviderOnFirstParameter" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
|
||||
@ -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()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
)
|
||||
}
|
||||
@ -1740,4 +1740,44 @@ private val _BreezyWeather = materialIcon("Icons.Rounded.BreezyWeather") {
|
||||
}
|
||||
|
||||
val Icons.Rounded.BreezyWeather
|
||||
get() = _BreezyWeather
|
||||
get() = _BreezyWeather
|
||||
|
||||
val _CutCorner = materialIcon("Icons.Rounded.CutCorner") {
|
||||
materialPath {
|
||||
moveTo(2f, 3f)
|
||||
verticalLineTo(22f)
|
||||
horizontalLineTo(21f)
|
||||
verticalLineTo(11.585938f)
|
||||
lineTo(12.414063f, 3f)
|
||||
close()
|
||||
moveToRelative(2f, 2f)
|
||||
horizontalLineToRelative(7.585938f)
|
||||
lineTo(19f, 12.414063f)
|
||||
verticalLineTo(20f)
|
||||
horizontalLineTo(4f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
val Icons.Rounded.CutCorner
|
||||
get() = _CutCorner
|
||||
|
||||
val _RoundedCornerAlt = materialIcon("Icons.Rounded.RoundedCornerAlt") {
|
||||
materialPath {
|
||||
moveTo(2f, 3f)
|
||||
verticalLineTo(22f)
|
||||
horizontalLineTo(21f)
|
||||
verticalLineTo(12f)
|
||||
curveTo(21f, 7.0412819f, 16.958718f, 3f, 12f, 3f)
|
||||
close()
|
||||
moveToRelative(2f, 2f)
|
||||
horizontalLineToRelative(8f)
|
||||
curveToRelative(3.877838f, 0f, 7f, 3.1221621f, 7f, 7f)
|
||||
verticalLineToRelative(8f)
|
||||
horizontalLineTo(4f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
val Icons.Rounded.RoundedCornerAlt
|
||||
get() = _RoundedCornerAlt
|
||||
@ -438,6 +438,11 @@
|
||||
<string name="preference_mdy_color_source">Source for dynamic colors</string>
|
||||
<string name="preference_mdy_color_source_system">System</string>
|
||||
<string name="preference_mdy_color_source_wallpaper">Wallpaper</string>
|
||||
<string name="preference_screen_shapes">Shapes</string>
|
||||
<string name="preference_shapes_default">Default</string>
|
||||
<string name="preference_shapes_extra_round">Extra round</string>
|
||||
<string name="preference_shapes_rect">Rectangualar</string>
|
||||
<string name="preference_shapes_base">Base shape</string>
|
||||
<string name="preference_font">Font</string>
|
||||
<string name="preference_font_system">System default</string>
|
||||
<string name="preference_screen_about">About</string>
|
||||
@ -819,7 +824,9 @@
|
||||
<string name="note_widget_file_write_error">Error saving note</string>
|
||||
<string name="note_widget_file_write_error_description">The note could not be written to the linked file. Possibly, it has been moved or deleted. A copy has been saved to the launcher\'s internal storage.</string>
|
||||
<string name="confirmation_delete_color_scheme">Do you really want to delete the color scheme %1$s\?</string>
|
||||
<string name="confirmation_delete_shapes_scheme">Do you really want to delete the shapes scheme %1$s\?</string>
|
||||
<string name="new_color_scheme_name">New color scheme</string>
|
||||
<string name="new_shapes_name">New shapes</string>
|
||||
<string name="theme_color_scheme_system_default">Use system default</string>
|
||||
<string name="theme_color_scheme_autogenerate">From primary color</string>
|
||||
<string name="theme_color_scheme_palette_color">Palette</string>
|
||||
|
||||
@ -11,7 +11,10 @@ data class LauncherSettingsData internal constructor(
|
||||
val schemaVersion: Int = 5,
|
||||
|
||||
val uiColorScheme: ColorScheme = ColorScheme.System,
|
||||
val uiTheme: ThemeDescriptor = ThemeDescriptor.Default,
|
||||
@JsonNames("uiTheme")
|
||||
val uiColors: ColorsDescriptor = ColorsDescriptor.Default,
|
||||
val uiShapes: ShapesDescriptor = ShapesDescriptor.Default,
|
||||
|
||||
val uiCompatModeColors: Boolean = false,
|
||||
val uiFont: Font = Font.Outfit,
|
||||
@Deprecated("No longer in use, only used for migration")
|
||||
@ -121,8 +124,10 @@ data class LauncherSettingsData internal constructor(
|
||||
val systemBarsNavColors: SystemBarColors = SystemBarColors.Auto,
|
||||
|
||||
val surfacesOpacity: Float = 1f,
|
||||
@Deprecated("Replaces with shape schemes")
|
||||
val surfacesRadius: Int = 24,
|
||||
val surfacesBorderWidth: Int = 0,
|
||||
@Deprecated("Replaces with shape schemes")
|
||||
val surfacesShape: SurfaceShape = SurfaceShape.Rounded,
|
||||
|
||||
val widgetsEditButton: Boolean = true,
|
||||
@ -201,20 +206,45 @@ enum class Font {
|
||||
|
||||
|
||||
@Serializable
|
||||
sealed interface ThemeDescriptor {
|
||||
sealed interface ColorsDescriptor {
|
||||
@Serializable
|
||||
@SerialName("default")
|
||||
data object Default : ThemeDescriptor
|
||||
data object Default : ColorsDescriptor
|
||||
|
||||
@Serializable
|
||||
@SerialName("bw")
|
||||
data object BlackAndWhite : ThemeDescriptor
|
||||
data object BlackAndWhite : ColorsDescriptor
|
||||
|
||||
@Serializable
|
||||
@SerialName("custom")
|
||||
data class Custom(
|
||||
val id: String,
|
||||
) : ThemeDescriptor
|
||||
) : ColorsDescriptor
|
||||
}
|
||||
|
||||
@Serializable
|
||||
sealed interface ShapesDescriptor {
|
||||
@Serializable
|
||||
@SerialName("default")
|
||||
data object Default : ShapesDescriptor
|
||||
|
||||
@Serializable
|
||||
@SerialName("cut")
|
||||
data object Cut : ShapesDescriptor
|
||||
|
||||
@Serializable
|
||||
@SerialName("extra_round")
|
||||
data object ExtraRound : ShapesDescriptor
|
||||
|
||||
@Serializable
|
||||
@SerialName("rect")
|
||||
data object Rect : ShapesDescriptor
|
||||
|
||||
@Serializable
|
||||
@SerialName("custom")
|
||||
data class Custom(
|
||||
val id: String,
|
||||
) : ShapesDescriptor
|
||||
}
|
||||
|
||||
internal enum class ClockWidgetStyleEnum {
|
||||
|
||||
@ -7,16 +7,14 @@ import de.mm20.launcher2.preferences.LauncherDataStore
|
||||
import de.mm20.launcher2.preferences.ScreenOrientation
|
||||
import de.mm20.launcher2.preferences.SearchBarColors
|
||||
import de.mm20.launcher2.preferences.SearchBarStyle
|
||||
import de.mm20.launcher2.preferences.SurfaceShape
|
||||
import de.mm20.launcher2.preferences.SystemBarColors
|
||||
import de.mm20.launcher2.preferences.ThemeDescriptor
|
||||
import de.mm20.launcher2.preferences.ColorsDescriptor
|
||||
import de.mm20.launcher2.preferences.ShapesDescriptor
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
data class CardStyle(
|
||||
val opacity: Float = 1f,
|
||||
val cornerRadius: Int = 0,
|
||||
val shape: SurfaceShape = SurfaceShape.Rounded,
|
||||
val borderWidth: Int = 0,
|
||||
)
|
||||
|
||||
@ -90,8 +88,6 @@ class UiSettings internal constructor(
|
||||
get() = launcherDataStore.data.map {
|
||||
CardStyle(
|
||||
opacity = it.surfacesOpacity,
|
||||
cornerRadius = it.surfacesRadius,
|
||||
shape = it.surfacesShape,
|
||||
borderWidth = it.surfacesBorderWidth,
|
||||
)
|
||||
}
|
||||
@ -102,24 +98,12 @@ class UiSettings internal constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun setCardRadius(radius: Int) {
|
||||
launcherDataStore.update {
|
||||
it.copy(surfacesRadius = radius)
|
||||
}
|
||||
}
|
||||
|
||||
fun setCardBorderWidth(borderWidth: Int) {
|
||||
launcherDataStore.update {
|
||||
it.copy(surfacesBorderWidth = borderWidth)
|
||||
}
|
||||
}
|
||||
|
||||
fun setCardShape(shape: SurfaceShape) {
|
||||
launcherDataStore.update {
|
||||
it.copy(surfacesShape = shape)
|
||||
}
|
||||
}
|
||||
|
||||
val dimWallpaper
|
||||
get() = launcherDataStore.data.map {
|
||||
it.wallpaperDim
|
||||
@ -302,14 +286,25 @@ class UiSettings internal constructor(
|
||||
}
|
||||
|
||||
|
||||
val theme
|
||||
val colors
|
||||
get() = launcherDataStore.data.map {
|
||||
it.uiTheme
|
||||
it.uiColors
|
||||
}.distinctUntilChanged()
|
||||
|
||||
fun setTheme(theme: ThemeDescriptor) {
|
||||
fun setColors(colors: ColorsDescriptor) {
|
||||
launcherDataStore.update {
|
||||
it.copy(uiTheme = theme)
|
||||
it.copy(uiColors = colors)
|
||||
}
|
||||
}
|
||||
|
||||
val shapes
|
||||
get() = launcherDataStore.data.map {
|
||||
it.uiShapes
|
||||
}.distinctUntilChanged()
|
||||
|
||||
fun setShapes(shapes: ShapesDescriptor) {
|
||||
launcherDataStore.update {
|
||||
it.copy(uiShapes = shapes)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -18,7 +18,8 @@ import de.mm20.launcher2.database.entities.IconPackEntity
|
||||
import de.mm20.launcher2.database.entities.PluginEntity
|
||||
import de.mm20.launcher2.database.entities.SavedSearchableEntity
|
||||
import de.mm20.launcher2.database.entities.SearchActionEntity
|
||||
import de.mm20.launcher2.database.entities.ThemeEntity
|
||||
import de.mm20.launcher2.database.entities.ColorsEntity
|
||||
import de.mm20.launcher2.database.entities.ShapesEntity
|
||||
import de.mm20.launcher2.database.entities.WidgetEntity
|
||||
import de.mm20.launcher2.database.migrations.Migration_10_11
|
||||
import de.mm20.launcher2.database.migrations.Migration_11_12
|
||||
@ -37,6 +38,7 @@ import de.mm20.launcher2.database.migrations.Migration_23_24
|
||||
import de.mm20.launcher2.database.migrations.Migration_24_25
|
||||
import de.mm20.launcher2.database.migrations.Migration_25_26
|
||||
import de.mm20.launcher2.database.migrations.Migration_26_27
|
||||
import de.mm20.launcher2.database.migrations.Migration_27_28
|
||||
import de.mm20.launcher2.database.migrations.Migration_6_7
|
||||
import de.mm20.launcher2.database.migrations.Migration_7_8
|
||||
import de.mm20.launcher2.database.migrations.Migration_8_9
|
||||
@ -54,9 +56,10 @@ import java.util.UUID
|
||||
WidgetEntity::class,
|
||||
CustomAttributeEntity::class,
|
||||
SearchActionEntity::class,
|
||||
ThemeEntity::class,
|
||||
ColorsEntity::class,
|
||||
PluginEntity::class,
|
||||
], version = 27, exportSchema = true
|
||||
ShapesEntity::class,
|
||||
], version = 28, exportSchema = true
|
||||
)
|
||||
@TypeConverters(ComponentNameConverter::class)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
@ -156,6 +159,7 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
Migration_24_25(),
|
||||
Migration_25_26(),
|
||||
Migration_26_27(),
|
||||
Migration_27_28(),
|
||||
).build()
|
||||
if (_instance == null) _instance = instance
|
||||
return instance
|
||||
|
||||
@ -4,30 +4,52 @@ import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import androidx.room.Update
|
||||
import de.mm20.launcher2.database.entities.ThemeEntity
|
||||
import de.mm20.launcher2.database.entities.ColorsEntity
|
||||
import de.mm20.launcher2.database.entities.ShapesEntity
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import java.util.UUID
|
||||
|
||||
@Dao
|
||||
interface ThemeDao {
|
||||
@Query("SELECT * FROM Theme")
|
||||
fun getAll(): Flow<List<ThemeEntity>>
|
||||
fun getAllColors(): Flow<List<ColorsEntity>>
|
||||
|
||||
@Query("SELECT * FROM Shapes")
|
||||
fun getAllShapes(): Flow<List<ShapesEntity>>
|
||||
|
||||
@Query("SELECT * FROM Theme WHERE id = :id LIMIT 1")
|
||||
fun get(id: UUID): Flow<ThemeEntity?>
|
||||
fun getColors(id: UUID): Flow<ColorsEntity?>
|
||||
|
||||
@Query("SELECT * FROM Shapes WHERE id = :id LIMIT 1")
|
||||
fun getShapes(id: UUID): Flow<ShapesEntity?>
|
||||
|
||||
@Insert
|
||||
suspend fun insert(theme: ThemeEntity)
|
||||
suspend fun insertColors(colors: ColorsEntity)
|
||||
|
||||
@Insert
|
||||
suspend fun insertShapes(shapes: ShapesEntity)
|
||||
|
||||
@Update
|
||||
suspend fun update(theme: ThemeEntity)
|
||||
suspend fun updateColors(colors: ColorsEntity)
|
||||
|
||||
@Update
|
||||
suspend fun updateShapes(shapes: ShapesEntity)
|
||||
|
||||
@Query("DELETE FROM Theme WHERE id = :id")
|
||||
suspend fun delete(id: UUID)
|
||||
suspend fun deleteColors(id: UUID)
|
||||
|
||||
@Query("DELETE FROM Shapes WHERE id = :id")
|
||||
suspend fun deleteShapes(id: UUID)
|
||||
|
||||
@Query("DELETE FROM Theme")
|
||||
suspend fun deleteAll()
|
||||
suspend fun deleteAllColors()
|
||||
|
||||
@Query("DELETE FROM Shapes")
|
||||
suspend fun deleteAllShapes()
|
||||
|
||||
@Insert
|
||||
fun insertAll(themes: List<ThemeEntity>)
|
||||
fun insertAllColors(colors: List<ColorsEntity>)
|
||||
|
||||
@Insert
|
||||
fun insertAllShapes(shapes: List<ShapesEntity>)
|
||||
}
|
||||
@ -5,7 +5,7 @@ import androidx.room.PrimaryKey
|
||||
import java.util.UUID
|
||||
|
||||
@Entity(tableName = "Theme")
|
||||
data class ThemeEntity(
|
||||
data class ColorsEntity(
|
||||
@PrimaryKey val id: UUID,
|
||||
val name: String,
|
||||
|
||||
@ -0,0 +1,22 @@
|
||||
package de.mm20.launcher2.database.entities
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import java.util.UUID
|
||||
|
||||
@Entity(tableName = "Shapes")
|
||||
data class ShapesEntity(
|
||||
@PrimaryKey val id: UUID,
|
||||
val name: String,
|
||||
|
||||
val baseShape: String,
|
||||
|
||||
val extraSmall: String? = null,
|
||||
val small: String? = null,
|
||||
val medium: String? = null,
|
||||
val large: String? = null,
|
||||
val largeIncreased: String? = null,
|
||||
val extraLarge: String? = null,
|
||||
val extraLargeIncreased: String? = null,
|
||||
val extraExtraLarge: String? = null,
|
||||
)
|
||||
@ -0,0 +1,27 @@
|
||||
package de.mm20.launcher2.database.migrations
|
||||
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
|
||||
class Migration_27_28: Migration(27, 28) {
|
||||
|
||||
override fun migrate(db: SupportSQLiteDatabase) {
|
||||
db.execSQL(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS `Shapes` (
|
||||
`id` BLOB NOT NULL PRIMARY KEY,
|
||||
`name` TEXT NOT NULL,
|
||||
`baseShape` TEXT NOT NULL,
|
||||
`extraSmall` TEXT,
|
||||
`small` TEXT,
|
||||
`medium` TEXT,
|
||||
`large` TEXT,
|
||||
`largeIncreased` TEXT,
|
||||
`extraLarge` TEXT,
|
||||
`extraLargeIncreased` TEXT,
|
||||
`extraExtraLarge` TEXT
|
||||
)
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -1,138 +1,13 @@
|
||||
package de.mm20.launcher2.themes
|
||||
|
||||
import de.mm20.launcher2.database.entities.ThemeEntity
|
||||
import de.mm20.launcher2.database.entities.ColorsEntity
|
||||
import hct.Hct
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import java.util.UUID
|
||||
|
||||
enum class CorePaletteColor {
|
||||
Primary,
|
||||
Secondary,
|
||||
Tertiary,
|
||||
Neutral,
|
||||
NeutralVariant,
|
||||
Error;
|
||||
|
||||
override fun toString(): String {
|
||||
return when (this) {
|
||||
Primary -> "p"
|
||||
Secondary -> "s"
|
||||
Tertiary -> "t"
|
||||
Neutral -> "n"
|
||||
NeutralVariant -> "nv"
|
||||
Error -> "e"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun CorePaletteColor(color: String): CorePaletteColor? {
|
||||
return when (color) {
|
||||
"p" -> CorePaletteColor.Primary
|
||||
"s" -> CorePaletteColor.Secondary
|
||||
"t" -> CorePaletteColor.Tertiary
|
||||
"n" -> CorePaletteColor.Neutral
|
||||
"nv" -> CorePaletteColor.NeutralVariant
|
||||
"e" -> CorePaletteColor.Error
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface Color
|
||||
|
||||
internal fun Color(string: String?): Color? {
|
||||
if (string == null) return null
|
||||
if (string.startsWith("#")) {
|
||||
return StaticColor(string.substring(1).toLongOrNull(16)?.toInt() ?: return null)
|
||||
}
|
||||
if (string.startsWith("$")) {
|
||||
val parts = string.substring(1).split(".").takeIf { it.size == 2 } ?: return null
|
||||
val color = CorePaletteColor(parts[0]) ?: return null
|
||||
return ColorRef(
|
||||
color = color,
|
||||
tone = parts[1].toIntOrNull() ?: return null,
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
data class ColorRef(
|
||||
val color: CorePaletteColor,
|
||||
val tone: Int,
|
||||
) : Color {
|
||||
override fun toString(): String {
|
||||
return "\$$color.$tone"
|
||||
}
|
||||
}
|
||||
|
||||
@JvmInline
|
||||
value class StaticColor(val color: Int) : Color {
|
||||
override fun toString(): String {
|
||||
return "#${color.toUInt().toString(16).padStart(8, '0')}"
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class CorePalette<out T : Int?>(
|
||||
val primary: T,
|
||||
val secondary: T,
|
||||
val tertiary: T,
|
||||
val neutral: T,
|
||||
val neutralVariant: T,
|
||||
val error: T,
|
||||
)
|
||||
|
||||
val EmptyCorePalette = CorePalette<Int?>(null, null, null, null, null, null)
|
||||
|
||||
typealias FullCorePalette = CorePalette<Int>
|
||||
typealias PartialCorePalette = CorePalette<Int?>
|
||||
|
||||
@Serializable
|
||||
data class ColorScheme<out T : Color?>(
|
||||
val primary: T,
|
||||
val onPrimary: T,
|
||||
val primaryContainer: T,
|
||||
val onPrimaryContainer: T,
|
||||
val secondary: T,
|
||||
val onSecondary: T,
|
||||
val secondaryContainer: T,
|
||||
val onSecondaryContainer: T,
|
||||
val tertiary: T,
|
||||
val onTertiary: T,
|
||||
val tertiaryContainer: T,
|
||||
val onTertiaryContainer: T,
|
||||
val error: T,
|
||||
val onError: T,
|
||||
val errorContainer: T,
|
||||
val onErrorContainer: T,
|
||||
val surface: T,
|
||||
val onSurface: T,
|
||||
val onSurfaceVariant: T,
|
||||
val outline: T,
|
||||
val outlineVariant: T,
|
||||
val inverseSurface: T,
|
||||
val inverseOnSurface: T,
|
||||
val inversePrimary: T,
|
||||
val surfaceDim: T,
|
||||
val surfaceBright: T,
|
||||
val surfaceContainerLowest: T,
|
||||
val surfaceContainerLow: T,
|
||||
val surfaceContainer: T,
|
||||
val surfaceContainerHigh: T,
|
||||
val surfaceContainerHighest: T,
|
||||
|
||||
val background: T,
|
||||
val onBackground: T,
|
||||
val surfaceTint: T,
|
||||
val scrim: T,
|
||||
val surfaceVariant: T,
|
||||
)
|
||||
|
||||
typealias FullColorScheme = ColorScheme<Color>
|
||||
typealias PartialColorScheme = ColorScheme<Color?>
|
||||
|
||||
@Serializable
|
||||
data class Theme(
|
||||
data class Colors(
|
||||
@Transient val id: UUID = UUID.randomUUID(),
|
||||
val builtIn: Boolean = false,
|
||||
val name: String,
|
||||
@ -141,7 +16,7 @@ data class Theme(
|
||||
val darkColorScheme: PartialColorScheme = DefaultDarkColorScheme,
|
||||
) {
|
||||
|
||||
constructor(entity: ThemeEntity) : this(
|
||||
constructor(entity: ColorsEntity) : this(
|
||||
id = entity.id,
|
||||
builtIn = false,
|
||||
name = entity.name,
|
||||
@ -154,86 +29,86 @@ data class Theme(
|
||||
error = entity.corePaletteE,
|
||||
),
|
||||
lightColorScheme = ColorScheme(
|
||||
primary = Color(entity.lightPrimary),
|
||||
onPrimary = Color(entity.lightOnPrimary),
|
||||
primaryContainer = Color(entity.lightPrimaryContainer),
|
||||
onPrimaryContainer = Color(entity.lightOnPrimaryContainer),
|
||||
secondary = Color(entity.lightSecondary),
|
||||
onSecondary = Color(entity.lightOnSecondary),
|
||||
secondaryContainer = Color(entity.lightSecondaryContainer),
|
||||
onSecondaryContainer = Color(entity.lightOnSecondaryContainer),
|
||||
tertiary = Color(entity.lightTertiary),
|
||||
onTertiary = Color(entity.lightOnTertiary),
|
||||
tertiaryContainer = Color(entity.lightTertiaryContainer),
|
||||
onTertiaryContainer = Color(entity.lightOnTertiaryContainer),
|
||||
error = Color(entity.lightError),
|
||||
onError = Color(entity.lightOnError),
|
||||
errorContainer = Color(entity.lightErrorContainer),
|
||||
onErrorContainer = Color(entity.lightOnErrorContainer),
|
||||
surface = Color(entity.lightSurface),
|
||||
onSurface = Color(entity.lightOnSurface),
|
||||
onSurfaceVariant = Color(entity.lightOnSurfaceVariant),
|
||||
outline = Color(entity.lightOutline),
|
||||
outlineVariant = Color(entity.lightOutlineVariant),
|
||||
inverseSurface = Color(entity.lightInverseSurface),
|
||||
inverseOnSurface = Color(entity.lightInverseOnSurface),
|
||||
inversePrimary = Color(entity.lightInversePrimary),
|
||||
surfaceDim = Color(entity.lightSurfaceDim),
|
||||
surfaceBright = Color(entity.lightSurfaceBright),
|
||||
surfaceContainerLowest = Color(entity.lightSurfaceContainerLowest),
|
||||
surfaceContainerLow = Color(entity.lightSurfaceContainerLow),
|
||||
surfaceContainer = Color(entity.lightSurfaceContainer),
|
||||
surfaceContainerHigh = Color(entity.lightSurfaceContainerHigh),
|
||||
surfaceContainerHighest = Color(entity.lightSurfaceContainerHighest),
|
||||
background = Color(entity.lightBackground),
|
||||
onBackground = Color(entity.lightOnBackground),
|
||||
surfaceTint = Color(entity.lightSurfaceTint),
|
||||
scrim = Color(entity.lightScrim),
|
||||
surfaceVariant = Color(entity.lightSurfaceVariant),
|
||||
primary = Color.fromString(entity.lightPrimary),
|
||||
onPrimary = Color.fromString(entity.lightOnPrimary),
|
||||
primaryContainer = Color.fromString(entity.lightPrimaryContainer),
|
||||
onPrimaryContainer = Color.fromString(entity.lightOnPrimaryContainer),
|
||||
secondary = Color.fromString(entity.lightSecondary),
|
||||
onSecondary = Color.fromString(entity.lightOnSecondary),
|
||||
secondaryContainer = Color.fromString(entity.lightSecondaryContainer),
|
||||
onSecondaryContainer = Color.fromString(entity.lightOnSecondaryContainer),
|
||||
tertiary = Color.fromString(entity.lightTertiary),
|
||||
onTertiary = Color.fromString(entity.lightOnTertiary),
|
||||
tertiaryContainer = Color.fromString(entity.lightTertiaryContainer),
|
||||
onTertiaryContainer = Color.fromString(entity.lightOnTertiaryContainer),
|
||||
error = Color.fromString(entity.lightError),
|
||||
onError = Color.fromString(entity.lightOnError),
|
||||
errorContainer = Color.fromString(entity.lightErrorContainer),
|
||||
onErrorContainer = Color.fromString(entity.lightOnErrorContainer),
|
||||
surface = Color.fromString(entity.lightSurface),
|
||||
onSurface = Color.fromString(entity.lightOnSurface),
|
||||
onSurfaceVariant = Color.fromString(entity.lightOnSurfaceVariant),
|
||||
outline = Color.fromString(entity.lightOutline),
|
||||
outlineVariant = Color.fromString(entity.lightOutlineVariant),
|
||||
inverseSurface = Color.fromString(entity.lightInverseSurface),
|
||||
inverseOnSurface = Color.fromString(entity.lightInverseOnSurface),
|
||||
inversePrimary = Color.fromString(entity.lightInversePrimary),
|
||||
surfaceDim = Color.fromString(entity.lightSurfaceDim),
|
||||
surfaceBright = Color.fromString(entity.lightSurfaceBright),
|
||||
surfaceContainerLowest = Color.fromString(entity.lightSurfaceContainerLowest),
|
||||
surfaceContainerLow = Color.fromString(entity.lightSurfaceContainerLow),
|
||||
surfaceContainer = Color.fromString(entity.lightSurfaceContainer),
|
||||
surfaceContainerHigh = Color.fromString(entity.lightSurfaceContainerHigh),
|
||||
surfaceContainerHighest = Color.fromString(entity.lightSurfaceContainerHighest),
|
||||
background = Color.fromString(entity.lightBackground),
|
||||
onBackground = Color.fromString(entity.lightOnBackground),
|
||||
surfaceTint = Color.fromString(entity.lightSurfaceTint),
|
||||
scrim = Color.fromString(entity.lightScrim),
|
||||
surfaceVariant = Color.fromString(entity.lightSurfaceVariant),
|
||||
),
|
||||
darkColorScheme = ColorScheme(
|
||||
primary = Color(entity.darkPrimary),
|
||||
onPrimary = Color(entity.darkOnPrimary),
|
||||
primaryContainer = Color(entity.darkPrimaryContainer),
|
||||
onPrimaryContainer = Color(entity.darkOnPrimaryContainer),
|
||||
secondary = Color(entity.darkSecondary),
|
||||
onSecondary = Color(entity.darkOnSecondary),
|
||||
secondaryContainer = Color(entity.darkSecondaryContainer),
|
||||
onSecondaryContainer = Color(entity.darkOnSecondaryContainer),
|
||||
tertiary = Color(entity.darkTertiary),
|
||||
onTertiary = Color(entity.darkOnTertiary),
|
||||
tertiaryContainer = Color(entity.darkTertiaryContainer),
|
||||
onTertiaryContainer = Color(entity.darkOnTertiaryContainer),
|
||||
error = Color(entity.darkError),
|
||||
onError = Color(entity.darkOnError),
|
||||
errorContainer = Color(entity.darkErrorContainer),
|
||||
onErrorContainer = Color(entity.darkOnErrorContainer),
|
||||
surface = Color(entity.darkSurface),
|
||||
onSurface = Color(entity.darkOnSurface),
|
||||
onSurfaceVariant = Color(entity.darkOnSurfaceVariant),
|
||||
outline = Color(entity.darkOutline),
|
||||
outlineVariant = Color(entity.darkOutlineVariant),
|
||||
inverseSurface = Color(entity.darkInverseSurface),
|
||||
inverseOnSurface = Color(entity.darkInverseOnSurface),
|
||||
inversePrimary = Color(entity.darkInversePrimary),
|
||||
surfaceDim = Color(entity.darkSurfaceDim),
|
||||
surfaceBright = Color(entity.darkSurfaceBright),
|
||||
surfaceContainerLowest = Color(entity.darkSurfaceContainerLowest),
|
||||
surfaceContainerLow = Color(entity.darkSurfaceContainerLow),
|
||||
surfaceContainer = Color(entity.darkSurfaceContainer),
|
||||
surfaceContainerHigh = Color(entity.darkSurfaceContainerHigh),
|
||||
surfaceContainerHighest = Color(entity.darkSurfaceContainerHighest),
|
||||
background = Color(entity.darkBackground),
|
||||
onBackground = Color(entity.darkOnBackground),
|
||||
surfaceTint = Color(entity.darkSurfaceTint),
|
||||
scrim = Color(entity.darkScrim),
|
||||
surfaceVariant = Color(entity.darkSurfaceVariant),
|
||||
primary = Color.fromString(entity.darkPrimary),
|
||||
onPrimary = Color.fromString(entity.darkOnPrimary),
|
||||
primaryContainer = Color.fromString(entity.darkPrimaryContainer),
|
||||
onPrimaryContainer = Color.fromString(entity.darkOnPrimaryContainer),
|
||||
secondary = Color.fromString(entity.darkSecondary),
|
||||
onSecondary = Color.fromString(entity.darkOnSecondary),
|
||||
secondaryContainer = Color.fromString(entity.darkSecondaryContainer),
|
||||
onSecondaryContainer = Color.fromString(entity.darkOnSecondaryContainer),
|
||||
tertiary = Color.fromString(entity.darkTertiary),
|
||||
onTertiary = Color.fromString(entity.darkOnTertiary),
|
||||
tertiaryContainer = Color.fromString(entity.darkTertiaryContainer),
|
||||
onTertiaryContainer = Color.fromString(entity.darkOnTertiaryContainer),
|
||||
error = Color.fromString(entity.darkError),
|
||||
onError = Color.fromString(entity.darkOnError),
|
||||
errorContainer = Color.fromString(entity.darkErrorContainer),
|
||||
onErrorContainer = Color.fromString(entity.darkOnErrorContainer),
|
||||
surface = Color.fromString(entity.darkSurface),
|
||||
onSurface = Color.fromString(entity.darkOnSurface),
|
||||
onSurfaceVariant = Color.fromString(entity.darkOnSurfaceVariant),
|
||||
outline = Color.fromString(entity.darkOutline),
|
||||
outlineVariant = Color.fromString(entity.darkOutlineVariant),
|
||||
inverseSurface = Color.fromString(entity.darkInverseSurface),
|
||||
inverseOnSurface = Color.fromString(entity.darkInverseOnSurface),
|
||||
inversePrimary = Color.fromString(entity.darkInversePrimary),
|
||||
surfaceDim = Color.fromString(entity.darkSurfaceDim),
|
||||
surfaceBright = Color.fromString(entity.darkSurfaceBright),
|
||||
surfaceContainerLowest = Color.fromString(entity.darkSurfaceContainerLowest),
|
||||
surfaceContainerLow = Color.fromString(entity.darkSurfaceContainerLow),
|
||||
surfaceContainer = Color.fromString(entity.darkSurfaceContainer),
|
||||
surfaceContainerHigh = Color.fromString(entity.darkSurfaceContainerHigh),
|
||||
surfaceContainerHighest = Color.fromString(entity.darkSurfaceContainerHighest),
|
||||
background = Color.fromString(entity.darkBackground),
|
||||
onBackground = Color.fromString(entity.darkOnBackground),
|
||||
surfaceTint = Color.fromString(entity.darkSurfaceTint),
|
||||
scrim = Color.fromString(entity.darkScrim),
|
||||
surfaceVariant = Color.fromString(entity.darkSurfaceVariant),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
internal fun toEntity(): ThemeEntity {
|
||||
return ThemeEntity(
|
||||
internal fun toEntity(): ColorsEntity {
|
||||
return ColorsEntity(
|
||||
id = id,
|
||||
name = name,
|
||||
corePaletteA1 = corePalette.primary,
|
||||
@ -320,6 +195,137 @@ data class Theme(
|
||||
}
|
||||
}
|
||||
|
||||
enum class CorePaletteColor {
|
||||
Primary,
|
||||
Secondary,
|
||||
Tertiary,
|
||||
Neutral,
|
||||
NeutralVariant,
|
||||
Error;
|
||||
|
||||
override fun toString(): String {
|
||||
return when (this) {
|
||||
Primary -> "p"
|
||||
Secondary -> "s"
|
||||
Tertiary -> "t"
|
||||
Neutral -> "n"
|
||||
NeutralVariant -> "nv"
|
||||
Error -> "e"
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromString(string: String): CorePaletteColor? {
|
||||
return when (string) {
|
||||
"p" -> Primary
|
||||
"s" -> Secondary
|
||||
"t" -> Tertiary
|
||||
"n" -> Neutral
|
||||
"nv" -> NeutralVariant
|
||||
"e" -> Error
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable(with = ColorSerializer::class)
|
||||
sealed interface Color {
|
||||
companion object {
|
||||
fun fromString(string: String?): Color? {
|
||||
|
||||
if (string == null) return null
|
||||
if (string.startsWith("#")) {
|
||||
return StaticColor(string.substring(1).toLongOrNull(16)?.toInt() ?: return null)
|
||||
}
|
||||
if (string.startsWith("$")) {
|
||||
val parts = string.substring(1).split(".").takeIf { it.size == 2 } ?: return null
|
||||
val color = CorePaletteColor.fromString(parts[0]) ?: return null
|
||||
return ColorRef(
|
||||
color = color,
|
||||
tone = parts[1].toIntOrNull() ?: return null,
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class ColorRef(
|
||||
val color: CorePaletteColor,
|
||||
val tone: Int,
|
||||
) : Color {
|
||||
override fun toString(): String {
|
||||
return "$$color.$tone"
|
||||
}
|
||||
}
|
||||
|
||||
@JvmInline
|
||||
value class StaticColor(val color: Int) : Color {
|
||||
override fun toString(): String {
|
||||
return "#${color.toUInt().toString(16).padStart(8, '0')}"
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class CorePalette<out T : Int?>(
|
||||
val primary: T,
|
||||
val secondary: T,
|
||||
val tertiary: T,
|
||||
val neutral: T,
|
||||
val neutralVariant: T,
|
||||
val error: T,
|
||||
)
|
||||
|
||||
val EmptyCorePalette = CorePalette<Int?>(null, null, null, null, null, null)
|
||||
|
||||
typealias FullCorePalette = CorePalette<Int>
|
||||
typealias PartialCorePalette = CorePalette<Int?>
|
||||
|
||||
@Serializable
|
||||
data class ColorScheme<out T : Color?>(
|
||||
val primary: T,
|
||||
val onPrimary: T,
|
||||
val primaryContainer: T,
|
||||
val onPrimaryContainer: T,
|
||||
val secondary: T,
|
||||
val onSecondary: T,
|
||||
val secondaryContainer: T,
|
||||
val onSecondaryContainer: T,
|
||||
val tertiary: T,
|
||||
val onTertiary: T,
|
||||
val tertiaryContainer: T,
|
||||
val onTertiaryContainer: T,
|
||||
val error: T,
|
||||
val onError: T,
|
||||
val errorContainer: T,
|
||||
val onErrorContainer: T,
|
||||
val surface: T,
|
||||
val onSurface: T,
|
||||
val onSurfaceVariant: T,
|
||||
val outline: T,
|
||||
val outlineVariant: T,
|
||||
val inverseSurface: T,
|
||||
val inverseOnSurface: T,
|
||||
val inversePrimary: T,
|
||||
val surfaceDim: T,
|
||||
val surfaceBright: T,
|
||||
val surfaceContainerLowest: T,
|
||||
val surfaceContainerLow: T,
|
||||
val surfaceContainer: T,
|
||||
val surfaceContainerHigh: T,
|
||||
val surfaceContainerHighest: T,
|
||||
|
||||
val background: T,
|
||||
val onBackground: T,
|
||||
val surfaceTint: T,
|
||||
val scrim: T,
|
||||
val surfaceVariant: T,
|
||||
)
|
||||
|
||||
typealias FullColorScheme = ColorScheme<Color>
|
||||
typealias PartialColorScheme = ColorScheme<Color?>
|
||||
|
||||
fun <T : Int?> CorePalette<T>.get(color: CorePaletteColor): T {
|
||||
return when (color) {
|
||||
CorePaletteColor.Primary -> primary
|
||||
@ -4,6 +4,10 @@ import java.util.UUID
|
||||
|
||||
|
||||
val DefaultThemeId = UUID(0L, 0L)
|
||||
val BlackAndWhiteThemeId = UUID(0L, 1L)
|
||||
val ExtraRoundShapesId = UUID(0L, 1L)
|
||||
val CutShapesId = UUID(0L, 2L)
|
||||
val RectShapesId = UUID(0L, 3L)
|
||||
|
||||
val DefaultLightColorScheme = ColorScheme<Color>(
|
||||
primary = ColorRef(CorePaletteColor.Primary, 40),
|
||||
@ -83,7 +87,6 @@ val DefaultDarkColorScheme = ColorScheme<Color>(
|
||||
scrim = ColorRef(CorePaletteColor.Neutral, 0),
|
||||
)
|
||||
|
||||
val BlackAndWhiteThemeId = UUID(0L, 1L)
|
||||
|
||||
val BlackAndWhiteLightColorScheme = ColorScheme<Color?>(
|
||||
primary = StaticColor(0xFF000000.toInt()),
|
||||
|
||||
@ -0,0 +1,67 @@
|
||||
package de.mm20.launcher2.themes
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.SerializationException
|
||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.modules.SerializersModule
|
||||
import kotlinx.serialization.modules.polymorphic
|
||||
|
||||
@Deprecated("Only used for backwards compatibility with old themes. New themes should use the new serialization format.")
|
||||
internal class LegacyColorRefSerializer: KSerializer<ColorRef> {
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("$", PrimitiveKind.STRING)
|
||||
|
||||
override fun deserialize(decoder: Decoder): ColorRef {
|
||||
return Color.fromString(decoder.decodeString()) as ColorRef
|
||||
}
|
||||
|
||||
override fun serialize(encoder: Encoder, value: ColorRef) {
|
||||
encoder.encodeString(value.toString())
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Only used for backwards compatibility with old themes. New themes should use the new serialization format.")
|
||||
internal class LegacyStaticColorSerializer: KSerializer<StaticColor> {
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("#", PrimitiveKind.STRING)
|
||||
override fun deserialize(decoder: Decoder): StaticColor {
|
||||
return Color.fromString(decoder.decodeString()) as StaticColor
|
||||
}
|
||||
|
||||
override fun serialize(encoder: Encoder, value: StaticColor) {
|
||||
encoder.encodeString(value.toString())
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Only used for backwards compatibility with old themes. New themes should use the new serialization format.")
|
||||
internal val legacyModule = SerializersModule {
|
||||
polymorphic(Color::class) {
|
||||
subclass(ColorRef::class, LegacyColorRefSerializer())
|
||||
subclass(StaticColor::class, LegacyStaticColorSerializer())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Deprecated("Only used for backwards compatibility with old themes. New themes should use the new serialization format.")
|
||||
val LegacyThemeJson = Json {
|
||||
serializersModule = legacyModule
|
||||
useArrayPolymorphism = true
|
||||
}
|
||||
|
||||
@Deprecated("Only used for backwards compatibility with old themes. New themes should use the new serialization format.")
|
||||
fun Colors.toLegacyJson(): String {
|
||||
return LegacyThemeJson.encodeToString(this)
|
||||
}
|
||||
|
||||
|
||||
@Deprecated("Only used for backwards compatibility with old themes. New themes should use the new serialization format.")
|
||||
fun Colors.Companion.fromLegacyJson(json: String): Colors {
|
||||
return try {
|
||||
LegacyThemeJson.decodeFromString(json)
|
||||
} catch (e: SerializationException) {
|
||||
throw IllegalArgumentException(e)
|
||||
}
|
||||
}
|
||||
@ -1,62 +1,39 @@
|
||||
package de.mm20.launcher2.themes
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.SerializationException
|
||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.modules.SerializersModule
|
||||
import kotlinx.serialization.modules.contextual
|
||||
import kotlinx.serialization.modules.polymorphic
|
||||
|
||||
internal class ColorRefSerializer: KSerializer<ColorRef> {
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("$", PrimitiveKind.STRING)
|
||||
internal class ColorSerializer: KSerializer<Color> {
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ColorSerializer", PrimitiveKind.STRING)
|
||||
|
||||
override fun deserialize(decoder: Decoder): ColorRef {
|
||||
return Color(decoder.decodeString()) as ColorRef
|
||||
}
|
||||
|
||||
override fun serialize(encoder: Encoder, value: ColorRef) {
|
||||
override fun serialize(
|
||||
encoder: Encoder,
|
||||
value: Color
|
||||
) {
|
||||
encoder.encodeString(value.toString())
|
||||
}
|
||||
}
|
||||
|
||||
internal class StaticColorSerializer: KSerializer<StaticColor> {
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("#", PrimitiveKind.STRING)
|
||||
override fun deserialize(decoder: Decoder): StaticColor {
|
||||
return Color(decoder.decodeString()) as StaticColor
|
||||
override fun deserialize(decoder: Decoder): Color {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun serialize(encoder: Encoder, value: StaticColor) {
|
||||
}
|
||||
|
||||
internal class ShapeSerializer: KSerializer<Shape> {
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ShapeSerializer", PrimitiveKind.STRING)
|
||||
|
||||
override fun serialize(
|
||||
encoder: Encoder,
|
||||
value: Shape
|
||||
) {
|
||||
encoder.encodeString(value.toString())
|
||||
}
|
||||
}
|
||||
|
||||
internal val module = SerializersModule {
|
||||
polymorphic(Color::class) {
|
||||
subclass(ColorRef::class, ColorRefSerializer())
|
||||
subclass(StaticColor::class, StaticColorSerializer())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
val ThemeJson = Json {
|
||||
serializersModule = module
|
||||
useArrayPolymorphism = true
|
||||
}
|
||||
|
||||
fun Theme.toJson(): String {
|
||||
return ThemeJson.encodeToString(this)
|
||||
}
|
||||
|
||||
fun Theme.Companion.fromJson(json: String): Theme {
|
||||
return try {
|
||||
ThemeJson.decodeFromString(json)
|
||||
} catch (e: SerializationException) {
|
||||
throw IllegalArgumentException(e)
|
||||
override fun deserialize(decoder: Decoder): Shape {
|
||||
return Shape.fromString(decoder.decodeString())!!
|
||||
}
|
||||
}
|
||||
120
data/themes/src/main/java/de/mm20/launcher2/themes/Shapes.kt
Normal file
120
data/themes/src/main/java/de/mm20/launcher2/themes/Shapes.kt
Normal file
@ -0,0 +1,120 @@
|
||||
package de.mm20.launcher2.themes
|
||||
|
||||
import de.mm20.launcher2.database.entities.ShapesEntity
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import java.util.UUID
|
||||
|
||||
@Serializable
|
||||
data class Shapes(
|
||||
@Transient val id: UUID = UUID.randomUUID(),
|
||||
val builtIn: Boolean = false,
|
||||
val name: String,
|
||||
val baseShape: Shape = Shape(
|
||||
corners = CornerStyle.Rounded,
|
||||
radii = intArrayOf(12, 12, 12, 12),
|
||||
),
|
||||
val extraSmall: Shape? = null,
|
||||
val small: Shape? = null,
|
||||
val medium: Shape? = null,
|
||||
val large: Shape? = null,
|
||||
val largeIncreased: Shape? = null,
|
||||
val extraLarge: Shape? = null,
|
||||
val extraLargeIncreased: Shape? = null,
|
||||
val extraExtraLarge: Shape? = null,
|
||||
) {
|
||||
constructor(entity: ShapesEntity) : this(
|
||||
id = entity.id,
|
||||
builtIn = false,
|
||||
name = entity.name,
|
||||
baseShape = Shape.fromString(entity.baseShape) ?: Shape(
|
||||
corners = CornerStyle.Rounded,
|
||||
radii = intArrayOf(12, 12, 12, 12)
|
||||
),
|
||||
extraSmall = Shape.fromString(entity.extraSmall),
|
||||
small = Shape.fromString(entity.small),
|
||||
medium = Shape.fromString(entity.medium),
|
||||
large = Shape.fromString(entity.large),
|
||||
largeIncreased = Shape.fromString(entity.largeIncreased),
|
||||
extraLarge = Shape.fromString(entity.extraLarge),
|
||||
extraLargeIncreased = Shape.fromString(entity.extraLargeIncreased),
|
||||
extraExtraLarge = Shape.fromString(entity.extraExtraLarge),
|
||||
)
|
||||
|
||||
internal fun toEntity(): ShapesEntity {
|
||||
return ShapesEntity(
|
||||
id = id,
|
||||
name = name,
|
||||
baseShape = baseShape.toString(),
|
||||
extraSmall = extraSmall?.toString(),
|
||||
small = small?.toString(),
|
||||
medium = medium?.toString(),
|
||||
large = large?.toString(),
|
||||
largeIncreased = largeIncreased?.toString(),
|
||||
extraLarge = extraLarge?.toString(),
|
||||
extraLargeIncreased = extraLargeIncreased?.toString(),
|
||||
extraExtraLarge = extraExtraLarge?.toString(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable(with = ShapeSerializer::class)
|
||||
data class Shape(
|
||||
/**
|
||||
* The style of the corners.
|
||||
* null to inherit the corner style from the base shape.
|
||||
*/
|
||||
val corners: CornerStyle? = null,
|
||||
/**
|
||||
* Radii in dp, in the order of top-start, top-end, bottom-end, bottom-start.
|
||||
* null to inherit the radius from the base shape.
|
||||
*/
|
||||
val radii: IntArray? = null,
|
||||
) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is Shape) return false
|
||||
return corners == other.corners &&
|
||||
radii.contentEquals(other.radii)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = corners.hashCode()
|
||||
result = 31 * result + radii.contentHashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
val type = when (corners) {
|
||||
CornerStyle.Rounded -> "r"
|
||||
CornerStyle.Cut -> "c"
|
||||
null -> "$"
|
||||
}
|
||||
val radii = radii?.joinToString("|") ?: ""
|
||||
return "$type.${radii}"
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromString(string: String?): Shape? {
|
||||
|
||||
if (string == null) return null
|
||||
val parts = string.split('.')
|
||||
val corners = when (parts[0]) {
|
||||
"r" -> CornerStyle.Rounded
|
||||
"c" -> CornerStyle.Cut
|
||||
else -> null
|
||||
}
|
||||
val radii = if (parts.size > 1 && parts[1].isNotEmpty()) {
|
||||
parts[1].split("|").map { it.toInt() }.toIntArray()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
return Shape(corners, radii)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class CornerStyle {
|
||||
Rounded,
|
||||
Cut,
|
||||
}
|
||||
@ -4,7 +4,8 @@ import android.content.Context
|
||||
import de.mm20.launcher2.backup.Backupable
|
||||
import de.mm20.launcher2.crashreporter.CrashReporter
|
||||
import de.mm20.launcher2.database.AppDatabase
|
||||
import de.mm20.launcher2.preferences.ThemeDescriptor
|
||||
import de.mm20.launcher2.preferences.ColorsDescriptor
|
||||
import de.mm20.launcher2.preferences.ShapesDescriptor
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
@ -16,7 +17,6 @@ import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.SerializationException
|
||||
import kotlinx.serialization.encodeToString
|
||||
import java.io.File
|
||||
import java.util.UUID
|
||||
|
||||
@ -26,50 +26,50 @@ class ThemeRepository(
|
||||
) : Backupable {
|
||||
private val scope = CoroutineScope(Dispatchers.IO + Job())
|
||||
|
||||
fun getThemes(): Flow<List<Theme>> {
|
||||
return database.themeDao().getAll().map {
|
||||
getBuiltInThemes() + it.map { Theme(it) }
|
||||
fun getAllColors(): Flow<List<Colors>> {
|
||||
return database.themeDao().getAllColors().map {
|
||||
getBuiltInColors() + it.map { Colors(it) }
|
||||
}
|
||||
}
|
||||
|
||||
fun getTheme(id: UUID): Flow<Theme?> {
|
||||
if (id == DefaultThemeId) return flowOf(getDefaultTheme())
|
||||
if (id == BlackAndWhiteThemeId) return flowOf(getBlackAndWhiteTheme())
|
||||
return database.themeDao().get(id).map { it?.let { Theme(it) } }.flowOn(Dispatchers.Default)
|
||||
fun getColors(id: UUID): Flow<Colors?> {
|
||||
if (id == DefaultThemeId) return flowOf(getDefaultColors())
|
||||
if (id == BlackAndWhiteThemeId) return flowOf(getBlackAndWhiteColors())
|
||||
return database.themeDao().getColors(id).map { it?.let { Colors(it) } }.flowOn(Dispatchers.Default)
|
||||
}
|
||||
|
||||
fun createTheme(theme: Theme) {
|
||||
fun createColors(colors: Colors) {
|
||||
scope.launch {
|
||||
database.themeDao().insert(theme.toEntity())
|
||||
database.themeDao().insertColors(colors.toEntity())
|
||||
}
|
||||
}
|
||||
|
||||
fun updateTheme(theme: Theme) {
|
||||
fun updateColors(colors: Colors) {
|
||||
scope.launch {
|
||||
database.themeDao().update(theme.toEntity())
|
||||
database.themeDao().updateColors(colors.toEntity())
|
||||
}
|
||||
}
|
||||
|
||||
fun getThemeOrDefault(theme: ThemeDescriptor?): Flow<Theme> {
|
||||
fun getColorsOrDefault(theme: ColorsDescriptor?): Flow<Colors> {
|
||||
return when(theme) {
|
||||
is ThemeDescriptor.BlackAndWhite -> flowOf(getBlackAndWhiteTheme())
|
||||
is ThemeDescriptor.Custom -> {
|
||||
is ColorsDescriptor.BlackAndWhite -> flowOf(getBlackAndWhiteColors())
|
||||
is ColorsDescriptor.Custom -> {
|
||||
val id = UUID.fromString(theme.id)
|
||||
getTheme(id).map { it ?: getDefaultTheme() }
|
||||
getColors(id).map { it ?: getDefaultColors() }
|
||||
}
|
||||
else -> flowOf(getDefaultTheme())
|
||||
else -> flowOf(getDefaultColors())
|
||||
}
|
||||
}
|
||||
|
||||
private fun getBuiltInThemes(): List<Theme> {
|
||||
private fun getBuiltInColors(): List<Colors> {
|
||||
return listOf(
|
||||
getDefaultTheme(),
|
||||
getBlackAndWhiteTheme(),
|
||||
getDefaultColors(),
|
||||
getBlackAndWhiteColors(),
|
||||
)
|
||||
}
|
||||
|
||||
fun getDefaultTheme(): Theme {
|
||||
return Theme(
|
||||
private fun getDefaultColors(): Colors {
|
||||
return Colors(
|
||||
id = DefaultThemeId,
|
||||
builtIn = true,
|
||||
name = context.getString(R.string.preference_colors_default),
|
||||
@ -79,8 +79,8 @@ class ThemeRepository(
|
||||
)
|
||||
}
|
||||
|
||||
private fun getBlackAndWhiteTheme(): Theme {
|
||||
return Theme(
|
||||
private fun getBlackAndWhiteColors(): Colors {
|
||||
return Colors(
|
||||
id = BlackAndWhiteThemeId,
|
||||
builtIn = true,
|
||||
name = context.getString(R.string.preference_colors_bw),
|
||||
@ -90,16 +90,127 @@ class ThemeRepository(
|
||||
)
|
||||
}
|
||||
|
||||
fun deleteTheme(theme: Theme) {
|
||||
fun deleteColors(colors: Colors) {
|
||||
scope.launch {
|
||||
database.themeDao().delete(theme.id)
|
||||
database.themeDao().deleteColors(colors.id)
|
||||
}
|
||||
}
|
||||
|
||||
fun getAllShapes(): Flow<List<Shapes>> {
|
||||
return database.themeDao().getAllShapes().map {
|
||||
getBuiltInShapes() + it.map { Shapes(it) }
|
||||
}
|
||||
}
|
||||
|
||||
fun getShapes(id: UUID): Flow<Shapes?> {
|
||||
if (id == DefaultThemeId) return flowOf(getDefaultShapes())
|
||||
if (id == ExtraRoundShapesId) return flowOf(getExtraRoundShapes())
|
||||
if (id == RectShapesId) return flowOf(getRectShapes())
|
||||
if (id == CutShapesId) return flowOf(getCutShapes())
|
||||
return database.themeDao().getShapes(id).map { it?.let { Shapes(it) } }.flowOn(Dispatchers.Default)
|
||||
}
|
||||
|
||||
fun createShapes(shapes: Shapes) {
|
||||
scope.launch {
|
||||
database.themeDao().insertShapes(shapes.toEntity())
|
||||
}
|
||||
}
|
||||
|
||||
fun updateShapes(shapes: Shapes) {
|
||||
scope.launch {
|
||||
database.themeDao().updateShapes(shapes.toEntity())
|
||||
}
|
||||
}
|
||||
|
||||
fun getShapesOrDefault(theme: ShapesDescriptor?): Flow<Shapes> {
|
||||
return when(theme) {
|
||||
is ShapesDescriptor.Custom -> {
|
||||
val id = UUID.fromString(theme.id)
|
||||
getShapes(id).map { it ?: getDefaultShapes() }
|
||||
}
|
||||
is ShapesDescriptor.ExtraRound -> flowOf(getExtraRoundShapes())
|
||||
is ShapesDescriptor.Rect -> flowOf(getRectShapes())
|
||||
is ShapesDescriptor.Cut -> flowOf(getCutShapes())
|
||||
else -> flowOf(getDefaultShapes())
|
||||
}
|
||||
}
|
||||
|
||||
private fun getBuiltInShapes(): List<Shapes> {
|
||||
return listOf(
|
||||
getDefaultShapes(),
|
||||
getExtraRoundShapes(),
|
||||
getRectShapes(),
|
||||
getCutShapes(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getDefaultShapes(): Shapes {
|
||||
return Shapes(
|
||||
id = DefaultThemeId,
|
||||
builtIn = true,
|
||||
name = context.getString(R.string.preference_shapes_default),
|
||||
baseShape = Shape(
|
||||
corners = CornerStyle.Rounded,
|
||||
radii = intArrayOf(12, 12, 12, 12),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun getCutShapes(): Shapes {
|
||||
return Shapes(
|
||||
id = CutShapesId,
|
||||
builtIn = true,
|
||||
name = context.getString(R.string.preference_cards_shape_cut),
|
||||
baseShape = Shape(
|
||||
corners = CornerStyle.Cut,
|
||||
radii = intArrayOf(12, 12, 12, 12),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun getExtraRoundShapes(): Shapes {
|
||||
return Shapes(
|
||||
id = ExtraRoundShapesId,
|
||||
builtIn = true,
|
||||
name = context.getString(R.string.preference_shapes_extra_round),
|
||||
baseShape = Shape(
|
||||
corners = CornerStyle.Rounded,
|
||||
radii = intArrayOf(24, 24, 24, 24),
|
||||
),
|
||||
extraLarge = Shape(
|
||||
radii = intArrayOf(36, 36, 36, 36),
|
||||
),
|
||||
extraLargeIncreased = Shape(
|
||||
radii = intArrayOf(40, 40, 40, 40),
|
||||
),
|
||||
extraExtraLarge = Shape(
|
||||
radii = intArrayOf(56, 56, 56, 56),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun getRectShapes(): Shapes {
|
||||
return Shapes(
|
||||
id = RectShapesId,
|
||||
builtIn = true,
|
||||
name = context.getString(R.string.preference_shapes_rect),
|
||||
baseShape = Shape(
|
||||
corners = CornerStyle.Rounded,
|
||||
radii = intArrayOf(0, 0, 0, 0),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun deleteShapes(shapes: Shapes) {
|
||||
scope.launch {
|
||||
database.themeDao().deleteShapes(shapes.id)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun backup(toDir: File) = withContext(Dispatchers.IO) {
|
||||
val dao = database.themeDao()
|
||||
val themes = dao.getAll().first().map { Theme(it) }
|
||||
val data = ThemeJson.encodeToString(themes)
|
||||
val colors = dao.getAllColors().first().map { Colors(it) }
|
||||
val data = LegacyThemeJson.encodeToString(colors)
|
||||
|
||||
val file = File(toDir, "themes.0000")
|
||||
file.bufferedWriter().use {
|
||||
@ -109,7 +220,7 @@ class ThemeRepository(
|
||||
|
||||
override suspend fun restore(fromDir: File) = withContext(Dispatchers.IO) {
|
||||
val dao = database.themeDao()
|
||||
dao.deleteAll()
|
||||
dao.deleteAllColors()
|
||||
|
||||
val files =
|
||||
fromDir.listFiles { _, name -> name.startsWith("themes.") }
|
||||
@ -117,8 +228,8 @@ class ThemeRepository(
|
||||
|
||||
for (file in files) {
|
||||
val data = file.inputStream().reader().readText()
|
||||
val themes: List<Theme> = try {
|
||||
ThemeJson.decodeFromString(data)
|
||||
val colors: List<Colors> = try {
|
||||
LegacyThemeJson.decodeFromString(data)
|
||||
} catch (e: SerializationException) {
|
||||
CrashReporter.logException(e)
|
||||
continue
|
||||
@ -126,7 +237,7 @@ class ThemeRepository(
|
||||
CrashReporter.logException(e)
|
||||
continue
|
||||
}
|
||||
dao.insertAll(themes.map { it.toEntity() })
|
||||
dao.insertAllColors(colors.map { it.toEntity() })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user