diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index 275c5bef..bb270b80 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -67,6 +67,7 @@
+
diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/common/ImportThemeSheet.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/common/ImportThemeSheet.kt
index de72b888..da651816 100644
--- a/app/ui/src/main/java/de/mm20/launcher2/ui/common/ImportThemeSheet.kt
+++ b/app/ui/src/main/java/de/mm20/launcher2/ui/common/ImportThemeSheet.kt
@@ -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
diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/common/ImportThemeSheetVM.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/common/ImportThemeSheetVM.kt
index f3a6e8c8..bd842022 100644
--- a/app/ui/src/main/java/de/mm20/launcher2/ui/common/ImportThemeSheetVM.kt
+++ b/app/ui/src/main/java/de/mm20/launcher2/ui/common/ImportThemeSheetVM.kt
@@ -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(null)
+ val colors = mutableStateOf(null)
val error = mutableStateOf(false)
val apply = mutableStateOf(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()))
}
}
}
\ No newline at end of file
diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/modifier/FadingEdges.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/modifier/FadingEdges.kt
index 1198eaa7..cda6ab17 100644
--- a/app/ui/src/main/java/de/mm20/launcher2/ui/modifier/FadingEdges.kt
+++ b/app/ui/src/main/java/de/mm20/launcher2/ui/modifier/FadingEdges.kt
@@ -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,
diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/modifier/Scrims.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/modifier/Scrims.kt
index bdffbec0..46fb2bad 100644
--- a/app/ui/src/main/java/de/mm20/launcher2/ui/modifier/Scrims.kt
+++ b/app/ui/src/main/java/de/mm20/launcher2/ui/modifier/Scrims.kt
@@ -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,
diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/SettingsActivity.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/SettingsActivity.kt
index cdbf0985..e9df7668 100644
--- a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/SettingsActivity.kt
+++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/SettingsActivity.kt
@@ -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()
diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/appearance/AppearanceSettingsScreen.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/appearance/AppearanceSettingsScreen.kt
index be178f0d..ca99dfe5 100644
--- a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/appearance/AppearanceSettingsScreen.kt
+++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/appearance/AppearanceSettingsScreen.kt
@@ -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(
diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/appearance/AppearanceSettingsScreenVM.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/appearance/AppearanceSettingsScreenVM.kt
index 4ffa4c73..49da7338 100644
--- a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/appearance/AppearanceSettingsScreenVM.kt
+++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/appearance/AppearanceSettingsScreenVM.kt
@@ -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
}
diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/cards/CardsSettingsScreen.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/cards/CardsSettingsScreen.kt
index 1d8b9f11..30b2db06 100644
--- a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/cards/CardsSettingsScreen.kt
+++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/cards/CardsSettingsScreen.kt
@@ -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,
diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/cards/CardsSettingsScreenVM.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/cards/CardsSettingsScreenVM.kt
index d5721b59..243674e7 100644
--- a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/cards/CardsSettingsScreenVM.kt
+++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/cards/CardsSettingsScreenVM.kt
@@ -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)
- }
}
\ No newline at end of file
diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/colorscheme/ThemePreferenceCategory.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/colorscheme/ColorSchemePreferenceCategory.kt
similarity index 86%
rename from app/ui/src/main/java/de/mm20/launcher2/ui/settings/colorscheme/ThemePreferenceCategory.kt
rename to app/ui/src/main/java/de/mm20/launcher2/ui/settings/colorscheme/ColorSchemePreferenceCategory.kt
index 97ad7496..3258550f 100644
--- a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/colorscheme/ThemePreferenceCategory.kt
+++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/colorscheme/ColorSchemePreferenceCategory.kt
@@ -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))
}
}
\ No newline at end of file
diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/colorscheme/ThemeSettingsScreen.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/colorscheme/ColorSchemeSettingsScreen.kt
similarity index 87%
rename from app/ui/src/main/java/de/mm20/launcher2/ui/settings/colorscheme/ThemeSettingsScreen.kt
rename to app/ui/src/main/java/de/mm20/launcher2/ui/settings/colorscheme/ColorSchemeSettingsScreen.kt
index 6784158b..c61d6437 100644
--- a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/colorscheme/ThemeSettingsScreen.kt
+++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/colorscheme/ColorSchemeSettingsScreen.kt
@@ -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),
)
},
) {
diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/colorscheme/ThemesSettingsScreen.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/colorscheme/ColorSchemesSettingsScreen.kt
similarity index 91%
rename from app/ui/src/main/java/de/mm20/launcher2/ui/settings/colorscheme/ThemesSettingsScreen.kt
rename to app/ui/src/main/java/de/mm20/launcher2/ui/settings/colorscheme/ColorSchemesSettingsScreen.kt
index 97f03e6a..d4649183 100644
--- a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/colorscheme/ThemesSettingsScreen.kt
+++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/colorscheme/ColorSchemesSettingsScreen.kt
@@ -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(null) }
+ var deleteColors by remember { mutableStateOf(null) }
var importThemeUri by remember { mutableStateOf(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)
diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/colorscheme/ThemesSettingsScreenVM.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/colorscheme/ColorSchemesSettingsScreenVM.kt
similarity index 55%
rename from app/ui/src/main/java/de/mm20/launcher2/ui/settings/colorscheme/ThemesSettingsScreenVM.kt
rename to app/ui/src/main/java/de/mm20/launcher2/ui/settings/colorscheme/ColorSchemesSettingsScreenVM.kt
index c4def5e7..a0acdb75 100644
--- a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/colorscheme/ThemesSettingsScreenVM.kt
+++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/colorscheme/ColorSchemesSettingsScreenVM.kt
@@ -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> = themeRepository.getThemes()
+ val colors: Flow> = themeRepository.getAllColors()
- fun getTheme(id: UUID): Flow {
- return themeRepository.getTheme(id)
+ fun getTheme(id: UUID): Flow {
+ 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)
)
diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/colorscheme/CorePaletteColorPreference.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/colorscheme/CorePaletteColorPreference.kt
index 025932b1..41482fd7 100644
--- a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/colorscheme/CorePaletteColorPreference.kt
+++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/colorscheme/CorePaletteColorPreference.kt
@@ -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,
)
}
diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/colorscheme/ThemeColorPreference.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/colorscheme/ThemeColorPreference.kt
index 9a525311..adea0f60 100644
--- a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/colorscheme/ThemeColorPreference.kt
+++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/colorscheme/ThemeColorPreference.kt
@@ -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,
)
}
diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/shapes/ShapeSchemeSettingsScreen.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/shapes/ShapeSchemeSettingsScreen.kt
new file mode 100644
index 00000000..4a2c1399
--- /dev/null
+++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/shapes/ShapeSchemeSettingsScreen.kt
@@ -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()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/shapes/ShapeSchemesSettingsScreen.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/shapes/ShapeSchemesSettingsScreen.kt
new file mode 100644
index 00000000..292abf64
--- /dev/null
+++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/shapes/ShapeSchemesSettingsScreen.kt
@@ -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(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
+ )
+ }
+ )
+ )
+
+}
\ No newline at end of file
diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/shapes/ShapeSchemesSettingsScreenVM.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/shapes/ShapeSchemesSettingsScreenVM.kt
new file mode 100644
index 00000000..1f5f8bc2
--- /dev/null
+++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/shapes/ShapeSchemesSettingsScreenVM.kt
@@ -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> = themeRepository.getAllShapes()
+
+ fun getShapes(id: UUID): Flow {
+ 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)
+ )
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/theme/LauncherTheme.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/theme/LauncherTheme.kt
index 37b9ee63..9cee27e7 100644
--- a/app/ui/src/main/java/de/mm20/launcher2/ui/theme/LauncherTheme.kt
+++ b/app/ui/src/main/java/de/mm20/launcher2/ui/theme/LauncherTheme.kt
@@ -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
)
}
diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/theme/colorscheme/BlackAndWhite.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/theme/colorscheme/BlackAndWhite.kt
deleted file mode 100644
index f284e0aa..00000000
--- a/app/ui/src/main/java/de/mm20/launcher2/ui/theme/colorscheme/BlackAndWhite.kt
+++ /dev/null
@@ -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,
-)
\ No newline at end of file
diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/theme/colorscheme/Custom.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/theme/colorscheme/ColorScheme.kt
similarity index 93%
rename from app/ui/src/main/java/de/mm20/launcher2/ui/theme/colorscheme/Custom.kt
rename to app/ui/src/main/java/de/mm20/launcher2/ui/theme/colorscheme/ColorScheme.kt
index 35fd8e93..bd0b3c25 100644
--- a/app/ui/src/main/java/de/mm20/launcher2/ui/theme/colorscheme/Custom.kt
+++ b/app/ui/src/main/java/de/mm20/launcher2/ui/theme/colorscheme/ColorScheme.kt
@@ -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
diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/theme/colorscheme/Default.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/theme/colorscheme/Default.kt
deleted file mode 100644
index cc8b5e96..00000000
--- a/app/ui/src/main/java/de/mm20/launcher2/ui/theme/colorscheme/Default.kt
+++ /dev/null
@@ -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),
-)
\ No newline at end of file
diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/theme/colorscheme/EasterEgg.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/theme/colorscheme/EasterEgg.kt
deleted file mode 100644
index 66e582d0..00000000
--- a/app/ui/src/main/java/de/mm20/launcher2/ui/theme/colorscheme/EasterEgg.kt
+++ /dev/null
@@ -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),
-)
\ No newline at end of file
diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/theme/colorscheme/Wallpaper.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/theme/colorscheme/Wallpaper.kt
deleted file mode 100644
index 2943537c..00000000
--- a/app/ui/src/main/java/de/mm20/launcher2/ui/theme/colorscheme/Wallpaper.kt
+++ /dev/null
@@ -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),
- )
-}
\ No newline at end of file
diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/theme/shapes/Shapes.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/theme/shapes/Shapes.kt
new file mode 100644
index 00000000..63d3ad0c
--- /dev/null
+++ b/app/ui/src/main/java/de/mm20/launcher2/ui/theme/shapes/Shapes.kt
@@ -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
+ )
+}
\ No newline at end of file
diff --git a/core/base/src/main/java/de/mm20/launcher2/icons/Icons.kt b/core/base/src/main/java/de/mm20/launcher2/icons/Icons.kt
index ff61acb0..ac975145 100644
--- a/core/base/src/main/java/de/mm20/launcher2/icons/Icons.kt
+++ b/core/base/src/main/java/de/mm20/launcher2/icons/Icons.kt
@@ -1740,4 +1740,44 @@ private val _BreezyWeather = materialIcon("Icons.Rounded.BreezyWeather") {
}
val Icons.Rounded.BreezyWeather
- get() = _BreezyWeather
\ No newline at end of file
+ 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
\ No newline at end of file
diff --git a/core/i18n/src/main/res/values/strings.xml b/core/i18n/src/main/res/values/strings.xml
index 6bd3c410..50bffc28 100644
--- a/core/i18n/src/main/res/values/strings.xml
+++ b/core/i18n/src/main/res/values/strings.xml
@@ -438,6 +438,11 @@
Source for dynamic colors
System
Wallpaper
+ Shapes
+ Default
+ Extra round
+ Rectangualar
+ Base shape
Font
System default
About
@@ -819,7 +824,9 @@
Error saving note
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.
Do you really want to delete the color scheme %1$s\?
+ Do you really want to delete the shapes scheme %1$s\?
New color scheme
+ New shapes
Use system default
From primary color
Palette
diff --git a/core/preferences/src/main/java/de/mm20/launcher2/preferences/LauncherSettingsData.kt b/core/preferences/src/main/java/de/mm20/launcher2/preferences/LauncherSettingsData.kt
index efde951b..d02c0c47 100644
--- a/core/preferences/src/main/java/de/mm20/launcher2/preferences/LauncherSettingsData.kt
+++ b/core/preferences/src/main/java/de/mm20/launcher2/preferences/LauncherSettingsData.kt
@@ -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 {
diff --git a/core/preferences/src/main/java/de/mm20/launcher2/preferences/ui/UiSettings.kt b/core/preferences/src/main/java/de/mm20/launcher2/preferences/ui/UiSettings.kt
index ae17f311..8e1d0e4c 100644
--- a/core/preferences/src/main/java/de/mm20/launcher2/preferences/ui/UiSettings.kt
+++ b/core/preferences/src/main/java/de/mm20/launcher2/preferences/ui/UiSettings.kt
@@ -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)
}
}
diff --git a/data/database/src/main/java/de/mm20/launcher2/database/AppDatabase.kt b/data/database/src/main/java/de/mm20/launcher2/database/AppDatabase.kt
index a41b50ce..a0ceb897 100644
--- a/data/database/src/main/java/de/mm20/launcher2/database/AppDatabase.kt
+++ b/data/database/src/main/java/de/mm20/launcher2/database/AppDatabase.kt
@@ -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
diff --git a/data/database/src/main/java/de/mm20/launcher2/database/daos/ThemeDao.kt b/data/database/src/main/java/de/mm20/launcher2/database/daos/ThemeDao.kt
index 6f6bc310..fc6956f8 100644
--- a/data/database/src/main/java/de/mm20/launcher2/database/daos/ThemeDao.kt
+++ b/data/database/src/main/java/de/mm20/launcher2/database/daos/ThemeDao.kt
@@ -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>
+ fun getAllColors(): Flow>
+
+ @Query("SELECT * FROM Shapes")
+ fun getAllShapes(): Flow>
@Query("SELECT * FROM Theme WHERE id = :id LIMIT 1")
- fun get(id: UUID): Flow
+ fun getColors(id: UUID): Flow
+
+ @Query("SELECT * FROM Shapes WHERE id = :id LIMIT 1")
+ fun getShapes(id: UUID): Flow
@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)
+ fun insertAllColors(colors: List)
+
+ @Insert
+ fun insertAllShapes(shapes: List)
}
\ No newline at end of file
diff --git a/data/database/src/main/java/de/mm20/launcher2/database/entities/ThemeEntity.kt b/data/database/src/main/java/de/mm20/launcher2/database/entities/ColorsEntity.kt
similarity index 99%
rename from data/database/src/main/java/de/mm20/launcher2/database/entities/ThemeEntity.kt
rename to data/database/src/main/java/de/mm20/launcher2/database/entities/ColorsEntity.kt
index b7a5fbf1..2567c8a2 100644
--- a/data/database/src/main/java/de/mm20/launcher2/database/entities/ThemeEntity.kt
+++ b/data/database/src/main/java/de/mm20/launcher2/database/entities/ColorsEntity.kt
@@ -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,
diff --git a/data/database/src/main/java/de/mm20/launcher2/database/entities/ShapesEntity.kt b/data/database/src/main/java/de/mm20/launcher2/database/entities/ShapesEntity.kt
new file mode 100644
index 00000000..78b8736f
--- /dev/null
+++ b/data/database/src/main/java/de/mm20/launcher2/database/entities/ShapesEntity.kt
@@ -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,
+)
\ No newline at end of file
diff --git a/data/database/src/main/java/de/mm20/launcher2/database/migrations/Migration_27_28.kt b/data/database/src/main/java/de/mm20/launcher2/database/migrations/Migration_27_28.kt
new file mode 100644
index 00000000..a42b654c
--- /dev/null
+++ b/data/database/src/main/java/de/mm20/launcher2/database/migrations/Migration_27_28.kt
@@ -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()
+ )
+ }
+}
\ No newline at end of file
diff --git a/data/themes/src/main/java/de/mm20/launcher2/themes/Theme.kt b/data/themes/src/main/java/de/mm20/launcher2/themes/Colors.kt
similarity index 65%
rename from data/themes/src/main/java/de/mm20/launcher2/themes/Theme.kt
rename to data/themes/src/main/java/de/mm20/launcher2/themes/Colors.kt
index 00ab1141..9ae19cc0 100644
--- a/data/themes/src/main/java/de/mm20/launcher2/themes/Theme.kt
+++ b/data/themes/src/main/java/de/mm20/launcher2/themes/Colors.kt
@@ -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(
- val primary: T,
- val secondary: T,
- val tertiary: T,
- val neutral: T,
- val neutralVariant: T,
- val error: T,
-)
-
-val EmptyCorePalette = CorePalette(null, null, null, null, null, null)
-
-typealias FullCorePalette = CorePalette
-typealias PartialCorePalette = CorePalette
-
-@Serializable
-data class ColorScheme(
- 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
-typealias PartialColorScheme = ColorScheme
-
-@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(
+ val primary: T,
+ val secondary: T,
+ val tertiary: T,
+ val neutral: T,
+ val neutralVariant: T,
+ val error: T,
+)
+
+val EmptyCorePalette = CorePalette(null, null, null, null, null, null)
+
+typealias FullCorePalette = CorePalette
+typealias PartialCorePalette = CorePalette
+
+@Serializable
+data class ColorScheme(
+ 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
+typealias PartialColorScheme = ColorScheme
+
fun CorePalette.get(color: CorePaletteColor): T {
return when (color) {
CorePaletteColor.Primary -> primary
diff --git a/data/themes/src/main/java/de/mm20/launcher2/themes/DefaultThemes.kt b/data/themes/src/main/java/de/mm20/launcher2/themes/DefaultThemes.kt
index 2d407cab..67e71e5a 100644
--- a/data/themes/src/main/java/de/mm20/launcher2/themes/DefaultThemes.kt
+++ b/data/themes/src/main/java/de/mm20/launcher2/themes/DefaultThemes.kt
@@ -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(
primary = ColorRef(CorePaletteColor.Primary, 40),
@@ -83,7 +87,6 @@ val DefaultDarkColorScheme = ColorScheme(
scrim = ColorRef(CorePaletteColor.Neutral, 0),
)
-val BlackAndWhiteThemeId = UUID(0L, 1L)
val BlackAndWhiteLightColorScheme = ColorScheme(
primary = StaticColor(0xFF000000.toInt()),
diff --git a/data/themes/src/main/java/de/mm20/launcher2/themes/LegacySerialization.kt b/data/themes/src/main/java/de/mm20/launcher2/themes/LegacySerialization.kt
new file mode 100644
index 00000000..b5f30138
--- /dev/null
+++ b/data/themes/src/main/java/de/mm20/launcher2/themes/LegacySerialization.kt
@@ -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 {
+ 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 {
+ 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)
+ }
+}
\ No newline at end of file
diff --git a/data/themes/src/main/java/de/mm20/launcher2/themes/Serialization.kt b/data/themes/src/main/java/de/mm20/launcher2/themes/Serialization.kt
index 8d183f1e..ce429c0f 100644
--- a/data/themes/src/main/java/de/mm20/launcher2/themes/Serialization.kt
+++ b/data/themes/src/main/java/de/mm20/launcher2/themes/Serialization.kt
@@ -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 {
- override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("$", PrimitiveKind.STRING)
+internal class ColorSerializer: KSerializer {
+ 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 {
- 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 {
+ 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())!!
}
}
\ No newline at end of file
diff --git a/data/themes/src/main/java/de/mm20/launcher2/themes/Shapes.kt b/data/themes/src/main/java/de/mm20/launcher2/themes/Shapes.kt
new file mode 100644
index 00000000..f876b2b3
--- /dev/null
+++ b/data/themes/src/main/java/de/mm20/launcher2/themes/Shapes.kt
@@ -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,
+}
\ No newline at end of file
diff --git a/data/themes/src/main/java/de/mm20/launcher2/themes/ThemeRepository.kt b/data/themes/src/main/java/de/mm20/launcher2/themes/ThemeRepository.kt
index 5b711983..bb9387a6 100644
--- a/data/themes/src/main/java/de/mm20/launcher2/themes/ThemeRepository.kt
+++ b/data/themes/src/main/java/de/mm20/launcher2/themes/ThemeRepository.kt
@@ -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> {
- return database.themeDao().getAll().map {
- getBuiltInThemes() + it.map { Theme(it) }
+ fun getAllColors(): Flow> {
+ return database.themeDao().getAllColors().map {
+ getBuiltInColors() + it.map { Colors(it) }
}
}
- fun getTheme(id: UUID): Flow {
- 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 {
+ 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 {
+ fun getColorsOrDefault(theme: ColorsDescriptor?): Flow {
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 {
+ private fun getBuiltInColors(): List {
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> {
+ return database.themeDao().getAllShapes().map {
+ getBuiltInShapes() + it.map { Shapes(it) }
+ }
+ }
+
+ fun getShapes(id: UUID): Flow {
+ 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 {
+ 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 {
+ 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 = try {
- ThemeJson.decodeFromString(data)
+ val colors: List = 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() })
}
}