(feat) shape schemes
This commit is contained in:
parent
17c4a51ca5
commit
cfb7bb0c72
1
.idea/inspectionProfiles/Project_Default.xml
generated
1
.idea/inspectionProfiles/Project_Default.xml
generated
@ -67,6 +67,7 @@
|
|||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
<inspection_tool class="PreviewParameterProviderOnFirstParameter" enabled="true" level="ERROR" enabled_by_default="true">
|
<inspection_tool class="PreviewParameterProviderOnFirstParameter" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
<option name="composableFile" value="true" />
|
<option name="composableFile" value="true" />
|
||||||
|
<option name="previewFile" value="true" />
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
<option name="composableFile" value="true" />
|
<option name="composableFile" value="true" />
|
||||||
|
|||||||
@ -40,7 +40,7 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
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.R
|
||||||
import de.mm20.launcher2.ui.component.BottomSheetDialog
|
import de.mm20.launcher2.ui.component.BottomSheetDialog
|
||||||
import de.mm20.launcher2.ui.component.LargeMessage
|
import de.mm20.launcher2.ui.component.LargeMessage
|
||||||
@ -63,7 +63,7 @@ fun ImportThemeSheet(
|
|||||||
viewModel.readTheme(context, uri)
|
viewModel.readTheme(context, uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
val theme by viewModel.theme
|
val theme by viewModel.colors
|
||||||
val error by viewModel.error
|
val error by viewModel.error
|
||||||
var apply by viewModel.apply
|
var apply by viewModel.apply
|
||||||
|
|
||||||
@ -132,13 +132,13 @@ fun ImportThemeSheet(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ThemePreview(
|
fun ThemePreview(
|
||||||
theme: Theme,
|
colors: Colors,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val darkMode = LocalDarkTheme.current
|
val darkMode = LocalDarkTheme.current
|
||||||
var darkTheme by remember { mutableStateOf(darkMode) }
|
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) {
|
Column(modifier = modifier) {
|
||||||
Row(
|
Row(
|
||||||
@ -146,7 +146,7 @@ fun ThemePreview(
|
|||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = theme.name,
|
text = colors.name,
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
|||||||
@ -5,11 +5,11 @@ import android.net.Uri
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
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.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.ThemeRepository
|
||||||
import de.mm20.launcher2.themes.fromJson
|
import de.mm20.launcher2.themes.fromLegacyJson
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
@ -20,12 +20,12 @@ class ImportThemeSheetVM : ViewModel(), KoinComponent {
|
|||||||
private val themeRepository: ThemeRepository by inject()
|
private val themeRepository: ThemeRepository by inject()
|
||||||
private val uiSettings: UiSettings by inject()
|
private val uiSettings: UiSettings by inject()
|
||||||
|
|
||||||
val theme = mutableStateOf<Theme?>(null)
|
val colors = mutableStateOf<Colors?>(null)
|
||||||
val error = mutableStateOf<Boolean>(false)
|
val error = mutableStateOf<Boolean>(false)
|
||||||
val apply = mutableStateOf<Boolean>(false)
|
val apply = mutableStateOf<Boolean>(false)
|
||||||
|
|
||||||
fun import() {
|
fun import() {
|
||||||
val theme = theme.value
|
val theme = colors.value
|
||||||
val apply = apply.value
|
val apply = apply.value
|
||||||
if (theme != null) {
|
if (theme != null) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
@ -37,30 +37,30 @@ class ImportThemeSheetVM : ViewModel(), KoinComponent {
|
|||||||
|
|
||||||
fun readTheme(context: Context, uri: Uri) {
|
fun readTheme(context: Context, uri: Uri) {
|
||||||
error.value = false
|
error.value = false
|
||||||
theme.value = null
|
colors.value = null
|
||||||
apply.value = true
|
apply.value = true
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val inputStream =
|
val inputStream =
|
||||||
context.contentResolver.openInputStream(uri) ?: return@launch
|
context.contentResolver.openInputStream(uri) ?: return@launch
|
||||||
val theme = inputStream.use {
|
val colors = inputStream.use {
|
||||||
val json = it.readBytes().toString(Charsets.UTF_8)
|
val json = it.readBytes().toString(Charsets.UTF_8)
|
||||||
try {
|
try {
|
||||||
Theme.fromJson(json)
|
Colors.fromLegacyJson(json)
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this@ImportThemeSheetVM.theme.value = theme
|
this@ImportThemeSheetVM.colors.value = colors
|
||||||
if (theme == null) {
|
if (colors == null) {
|
||||||
error.value = true
|
error.value = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun importTheme(theme: Theme, apply: Boolean) {
|
private fun importTheme(colors: Colors, apply: Boolean) {
|
||||||
themeRepository.createTheme(theme)
|
themeRepository.createColors(colors)
|
||||||
if (apply) {
|
if (apply) {
|
||||||
uiSettings.setTheme(ThemeDescriptor.Custom(theme.id.toString()))
|
uiSettings.setColors(ColorsDescriptor.Custom(colors.id.toString()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -28,7 +28,7 @@ fun Modifier.verticalFadingEdges(
|
|||||||
if(!enabled) return this
|
if(!enabled) return this
|
||||||
if (top == 0.dp && bottom == 0.dp) return this
|
if (top == 0.dp && bottom == 0.dp) return this
|
||||||
|
|
||||||
return this then drawWithContent {
|
return drawWithContent {
|
||||||
|
|
||||||
val topColors = if (top > 0.dp) createColors(
|
val topColors = if (top > 0.dp) createColors(
|
||||||
1f - amount,
|
1f - amount,
|
||||||
|
|||||||
@ -27,7 +27,7 @@ fun Modifier.verticalScrims(
|
|||||||
if (!enabled) return this
|
if (!enabled) return this
|
||||||
if (top == 0.dp && bottom == 0.dp) return this
|
if (top == 0.dp && bottom == 0.dp) return this
|
||||||
|
|
||||||
return this then drawWithCache {
|
return drawWithCache {
|
||||||
onDrawWithContent {
|
onDrawWithContent {
|
||||||
val topColors = if (top > 0.dp) createColors(
|
val topColors = if (top > 0.dp) createColors(
|
||||||
1f - amount,
|
1f - amount,
|
||||||
|
|||||||
@ -42,8 +42,8 @@ import de.mm20.launcher2.ui.settings.buildinfo.BuildInfoSettingsScreen
|
|||||||
import de.mm20.launcher2.ui.settings.calendarsearch.CalendarProviderSettingsScreen
|
import de.mm20.launcher2.ui.settings.calendarsearch.CalendarProviderSettingsScreen
|
||||||
import de.mm20.launcher2.ui.settings.calendarsearch.CalendarSearchSettingsScreen
|
import de.mm20.launcher2.ui.settings.calendarsearch.CalendarSearchSettingsScreen
|
||||||
import de.mm20.launcher2.ui.settings.cards.CardsSettingsScreen
|
import de.mm20.launcher2.ui.settings.cards.CardsSettingsScreen
|
||||||
import de.mm20.launcher2.ui.settings.colorscheme.ThemeSettingsScreen
|
import de.mm20.launcher2.ui.settings.colorscheme.ColorSchemeSettingsScreen
|
||||||
import de.mm20.launcher2.ui.settings.colorscheme.ThemesSettingsScreen
|
import de.mm20.launcher2.ui.settings.colorscheme.ColorSchemesSettingsScreen
|
||||||
import de.mm20.launcher2.ui.settings.contacts.ContactsSettingsScreen
|
import de.mm20.launcher2.ui.settings.contacts.ContactsSettingsScreen
|
||||||
import de.mm20.launcher2.ui.settings.crashreporter.CrashReportScreen
|
import de.mm20.launcher2.ui.settings.crashreporter.CrashReportScreen
|
||||||
import de.mm20.launcher2.ui.settings.crashreporter.CrashReporterScreen
|
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.plugins.PluginsSettingsScreen
|
||||||
import de.mm20.launcher2.ui.settings.search.SearchSettingsScreen
|
import de.mm20.launcher2.ui.settings.search.SearchSettingsScreen
|
||||||
import de.mm20.launcher2.ui.settings.searchactions.SearchActionsSettingsScreen
|
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.tags.TagsSettingsScreen
|
||||||
import de.mm20.launcher2.ui.settings.tasks.TasksIntegrationSettingsScreen
|
import de.mm20.launcher2.ui.settings.tasks.TasksIntegrationSettingsScreen
|
||||||
import de.mm20.launcher2.ui.settings.unitconverter.UnitConverterHelpSettingsScreen
|
import de.mm20.launcher2.ui.settings.unitconverter.UnitConverterHelpSettingsScreen
|
||||||
@ -160,11 +162,11 @@ class SettingsActivity : BaseActivity() {
|
|||||||
composable("settings/icons") {
|
composable("settings/icons") {
|
||||||
IconsSettingsScreen()
|
IconsSettingsScreen()
|
||||||
}
|
}
|
||||||
composable("settings/appearance/themes") {
|
composable("settings/appearance/colors") {
|
||||||
ThemesSettingsScreen()
|
ColorSchemesSettingsScreen()
|
||||||
}
|
}
|
||||||
composable(
|
composable(
|
||||||
"settings/appearance/themes/{id}",
|
"settings/appearance/colors/{id}",
|
||||||
arguments = listOf(navArgument("id") {
|
arguments = listOf(navArgument("id") {
|
||||||
nullable = false
|
nullable = false
|
||||||
})
|
})
|
||||||
@ -172,7 +174,21 @@ class SettingsActivity : BaseActivity() {
|
|||||||
val id = it.arguments?.getString("id")?.let {
|
val id = it.arguments?.getString("id")?.let {
|
||||||
UUID.fromString(it)
|
UUID.fromString(it)
|
||||||
} ?: return@composable
|
} ?: 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") {
|
composable("settings/appearance/cards") {
|
||||||
CardsSettingsScreen()
|
CardsSettingsScreen()
|
||||||
|
|||||||
@ -1,5 +1,10 @@
|
|||||||
package de.mm20.launcher2.ui.settings.appearance
|
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.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
@ -45,12 +50,17 @@ fun AppearanceSettingsScreen() {
|
|||||||
viewModel.setColorScheme(newValue)
|
viewModel.setColorScheme(newValue)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
PreferenceCategory {
|
||||||
Preference(
|
Preference(
|
||||||
title = stringResource(id = R.string.preference_screen_colors),
|
title = stringResource(id = R.string.preference_screen_colors),
|
||||||
summary = themeName,
|
summary = themeName,
|
||||||
onClick = {
|
onClick = {
|
||||||
navController?.navigate("settings/appearance/themes")
|
navController?.navigate("settings/appearance/colors")
|
||||||
}
|
},
|
||||||
|
icon = Icons.Rounded.Palette,
|
||||||
)
|
)
|
||||||
val font by viewModel.font.collectAsState()
|
val font by viewModel.font.collectAsState()
|
||||||
ListPreference(
|
ListPreference(
|
||||||
@ -68,7 +78,16 @@ fun AppearanceSettingsScreen() {
|
|||||||
getTypography(context, it.value)
|
getTypography(context, it.value)
|
||||||
}
|
}
|
||||||
Text(it.first, style = typography.titleMedium)
|
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(
|
Preference(
|
||||||
|
|||||||
@ -2,7 +2,6 @@ package de.mm20.launcher2.ui.settings.appearance
|
|||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import de.mm20.launcher2.icons.IconService
|
|
||||||
import de.mm20.launcher2.preferences.ColorScheme
|
import de.mm20.launcher2.preferences.ColorScheme
|
||||||
import de.mm20.launcher2.preferences.Font
|
import de.mm20.launcher2.preferences.Font
|
||||||
import de.mm20.launcher2.preferences.ui.UiSettings
|
import de.mm20.launcher2.preferences.ui.UiSettings
|
||||||
@ -26,8 +25,8 @@ class AppearanceSettingsScreenVM : ViewModel(), KoinComponent {
|
|||||||
uiSettings.setColorScheme(colorScheme)
|
uiSettings.setColorScheme(colorScheme)
|
||||||
}
|
}
|
||||||
|
|
||||||
val themeName = uiSettings.theme.flatMapLatest {
|
val themeName = uiSettings.colors.flatMapLatest {
|
||||||
themeRepository.getThemeOrDefault(it)
|
themeRepository.getColorsOrDefault(it)
|
||||||
}.map {
|
}.map {
|
||||||
it.name
|
it.name
|
||||||
}
|
}
|
||||||
|
|||||||
@ -47,28 +47,6 @@ fun CardsSettingsScreen() {
|
|||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
PreferenceCategory {
|
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(
|
SliderPreference(
|
||||||
title = stringResource(R.string.preference_cards_opacity),
|
title = stringResource(R.string.preference_cards_opacity),
|
||||||
icon = Icons.Rounded.Opacity,
|
icon = Icons.Rounded.Opacity,
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
package de.mm20.launcher2.ui.settings.cards
|
package de.mm20.launcher2.ui.settings.cards
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import de.mm20.launcher2.preferences.SurfaceShape
|
|
||||||
import de.mm20.launcher2.preferences.ui.UiSettings
|
import de.mm20.launcher2.preferences.ui.UiSettings
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
import org.koin.core.component.inject
|
import org.koin.core.component.inject
|
||||||
@ -15,15 +14,8 @@ class CardsSettingsScreenVM : ViewModel(), KoinComponent {
|
|||||||
uiSettings.setCardOpacity(opacity)
|
uiSettings.setCardOpacity(opacity)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setRadius(radius: Int) {
|
|
||||||
uiSettings.setCardRadius(radius)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setBorderWidth(borderWidth: Int) {
|
fun setBorderWidth(borderWidth: Int) {
|
||||||
uiSettings.setCardBorderWidth(borderWidth)
|
uiSettings.setCardBorderWidth(borderWidth)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setShape(shape: SurfaceShape) {
|
|
||||||
uiSettings.setCardShape(shape)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -1,16 +1,11 @@
|
|||||||
package de.mm20.launcher2.ui.settings.colorscheme
|
package de.mm20.launcher2.ui.settings.colorscheme
|
||||||
|
|
||||||
import androidx.compose.foundation.horizontalScroll
|
import androidx.compose.foundation.horizontalScroll
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Column
|
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.Row
|
||||||
import androidx.compose.foundation.layout.RowScope
|
import androidx.compose.foundation.layout.RowScope
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.rounded.DarkMode
|
import androidx.compose.material.icons.rounded.DarkMode
|
||||||
@ -31,7 +26,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ThemePreferenceCategory(
|
fun ColorSchemePreferenceCategory(
|
||||||
title: String,
|
title: String,
|
||||||
previewColorScheme: ColorScheme,
|
previewColorScheme: ColorScheme,
|
||||||
darkMode: Boolean,
|
darkMode: Boolean,
|
||||||
@ -95,15 +90,8 @@ fun ThemePreferenceCategory(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.horizontalScroll(rememberScrollState())
|
|
||||||
.padding(16.dp)
|
|
||||||
) {
|
|
||||||
colorPreferences()
|
colorPreferences()
|
||||||
}
|
HorizontalDivider(modifier = Modifier.padding(top = 8.dp))
|
||||||
HorizontalDivider()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -1,20 +1,16 @@
|
|||||||
package de.mm20.launcher2.ui.settings.colorscheme
|
package de.mm20.launcher2.ui.settings.colorscheme
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.horizontalScroll
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.IntrinsicSize
|
import androidx.compose.foundation.layout.IntrinsicSize
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.layout.wrapContentWidth
|
import androidx.compose.foundation.layout.wrapContentWidth
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.material.icons.Icons
|
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.Edit
|
||||||
import androidx.compose.material.icons.rounded.Lock
|
import androidx.compose.material.icons.rounded.Lock
|
||||||
import androidx.compose.material.icons.rounded.OpenInNew
|
import androidx.compose.material.icons.rounded.OpenInNew
|
||||||
@ -60,6 +56,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import de.mm20.launcher2.badges.Badge
|
||||||
import de.mm20.launcher2.icons.ColorLayer
|
import de.mm20.launcher2.icons.ColorLayer
|
||||||
import de.mm20.launcher2.icons.StaticLauncherIcon
|
import de.mm20.launcher2.icons.StaticLauncherIcon
|
||||||
import de.mm20.launcher2.icons.TintedIconLayer
|
import de.mm20.launcher2.icons.TintedIconLayer
|
||||||
@ -78,8 +75,8 @@ import palettes.CorePalette
|
|||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ThemeSettingsScreen(themeId: UUID) {
|
fun ColorSchemeSettingsScreen(themeId: UUID) {
|
||||||
val viewModel: ThemesSettingsScreenVM = viewModel()
|
val viewModel: ColorSchemesSettingsScreenVM = viewModel()
|
||||||
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val dark = LocalDarkTheme.current
|
val dark = LocalDarkTheme.current
|
||||||
@ -107,7 +104,13 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
var name by remember(theme) { mutableStateOf(theme?.name ?: "") }
|
var name by remember(theme) { mutableStateOf(theme?.name ?: "") }
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = { editName = false },
|
onDismissRequest = { editName = false },
|
||||||
text = { OutlinedTextField(value = name, onValueChange = { name = it }, singleLine = true) },
|
text = {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = name,
|
||||||
|
onValueChange = { name = it },
|
||||||
|
singleLine = true
|
||||||
|
)
|
||||||
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
@ -146,15 +149,10 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
stringResource(R.string.preference_custom_colors_corepalette),
|
stringResource(R.string.preference_custom_colors_corepalette),
|
||||||
style = MaterialTheme.typography.titleSmall,
|
style = MaterialTheme.typography.titleSmall,
|
||||||
color = MaterialTheme.colorScheme.secondary,
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
modifier = Modifier.padding(horizontal = 16.dp),
|
modifier = Modifier.padding(bottom = 16.dp, start = 16.dp, end = 16.dp),
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
)
|
)
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.horizontalScroll(rememberScrollState())
|
|
||||||
.padding(16.dp)
|
|
||||||
) {
|
|
||||||
CorePaletteColorPreference(
|
CorePaletteColorPreference(
|
||||||
title = "Primary",
|
title = "Primary",
|
||||||
value = theme?.corePalette?.primary,
|
value = theme?.corePalette?.primary,
|
||||||
@ -269,12 +267,11 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
HorizontalDivider(modifier = Modifier.padding(top = 8.dp))
|
||||||
HorizontalDivider()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
ThemePreferenceCategory(
|
ColorSchemePreferenceCategory(
|
||||||
title = "Primary colors",
|
title = "Primary colors",
|
||||||
previewColorScheme = previewColorScheme,
|
previewColorScheme = previewColorScheme,
|
||||||
darkMode = previewDarkTheme,
|
darkMode = previewDarkTheme,
|
||||||
@ -302,7 +299,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
defaultValue = selectedDefaultScheme.primary,
|
defaultValue = selectedDefaultScheme.primary,
|
||||||
modifier = Modifier.padding(end = 12.dp),
|
|
||||||
)
|
)
|
||||||
ThemeColorPreference(
|
ThemeColorPreference(
|
||||||
title = "On Primary",
|
title = "On Primary",
|
||||||
@ -326,7 +322,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
defaultValue = selectedDefaultScheme.onPrimary,
|
defaultValue = selectedDefaultScheme.onPrimary,
|
||||||
modifier = Modifier.padding(end = 12.dp),
|
|
||||||
)
|
)
|
||||||
ThemeColorPreference(
|
ThemeColorPreference(
|
||||||
title = "Primary Container",
|
title = "Primary Container",
|
||||||
@ -350,7 +345,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
defaultValue = selectedDefaultScheme.primaryContainer,
|
defaultValue = selectedDefaultScheme.primaryContainer,
|
||||||
modifier = Modifier.padding(end = 12.dp),
|
|
||||||
)
|
)
|
||||||
ThemeColorPreference(
|
ThemeColorPreference(
|
||||||
title = "On Primary Container",
|
title = "On Primary Container",
|
||||||
@ -374,7 +368,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
defaultValue = selectedDefaultScheme.onPrimaryContainer,
|
defaultValue = selectedDefaultScheme.onPrimaryContainer,
|
||||||
modifier = Modifier.padding(end = 12.dp),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
},
|
},
|
||||||
@ -419,7 +412,7 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
ThemePreferenceCategory(
|
ColorSchemePreferenceCategory(
|
||||||
title = "Secondary colors",
|
title = "Secondary colors",
|
||||||
previewColorScheme = previewColorScheme,
|
previewColorScheme = previewColorScheme,
|
||||||
darkMode = previewDarkTheme,
|
darkMode = previewDarkTheme,
|
||||||
@ -447,7 +440,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
defaultValue = selectedDefaultScheme.secondary,
|
defaultValue = selectedDefaultScheme.secondary,
|
||||||
modifier = Modifier.padding(end = 12.dp),
|
|
||||||
)
|
)
|
||||||
ThemeColorPreference(
|
ThemeColorPreference(
|
||||||
title = "On Secondary",
|
title = "On Secondary",
|
||||||
@ -471,7 +463,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
defaultValue = selectedDefaultScheme.onSecondary,
|
defaultValue = selectedDefaultScheme.onSecondary,
|
||||||
modifier = Modifier.padding(end = 12.dp),
|
|
||||||
)
|
)
|
||||||
ThemeColorPreference(
|
ThemeColorPreference(
|
||||||
title = "Secondary Container",
|
title = "Secondary Container",
|
||||||
@ -495,7 +486,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
defaultValue = selectedDefaultScheme.secondaryContainer,
|
defaultValue = selectedDefaultScheme.secondaryContainer,
|
||||||
modifier = Modifier.padding(end = 12.dp),
|
|
||||||
)
|
)
|
||||||
ThemeColorPreference(
|
ThemeColorPreference(
|
||||||
title = "On Secondary Container",
|
title = "On Secondary Container",
|
||||||
@ -519,7 +509,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
defaultValue = selectedDefaultScheme.onSecondaryContainer,
|
defaultValue = selectedDefaultScheme.onSecondaryContainer,
|
||||||
modifier = Modifier.padding(end = 12.dp),
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
@ -557,7 +546,7 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
ThemePreferenceCategory(
|
ColorSchemePreferenceCategory(
|
||||||
title = "Tertiary colors",
|
title = "Tertiary colors",
|
||||||
previewColorScheme = previewColorScheme,
|
previewColorScheme = previewColorScheme,
|
||||||
darkMode = previewDarkTheme,
|
darkMode = previewDarkTheme,
|
||||||
@ -585,7 +574,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
defaultValue = selectedDefaultScheme.tertiary,
|
defaultValue = selectedDefaultScheme.tertiary,
|
||||||
modifier = Modifier.padding(end = 12.dp),
|
|
||||||
)
|
)
|
||||||
ThemeColorPreference(
|
ThemeColorPreference(
|
||||||
title = "On Tertiary",
|
title = "On Tertiary",
|
||||||
@ -609,7 +597,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
defaultValue = selectedDefaultScheme.onTertiary,
|
defaultValue = selectedDefaultScheme.onTertiary,
|
||||||
modifier = Modifier.padding(end = 12.dp),
|
|
||||||
)
|
)
|
||||||
ThemeColorPreference(
|
ThemeColorPreference(
|
||||||
title = "Tertiary Container",
|
title = "Tertiary Container",
|
||||||
@ -633,7 +620,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
defaultValue = selectedDefaultScheme.tertiaryContainer,
|
defaultValue = selectedDefaultScheme.tertiaryContainer,
|
||||||
modifier = Modifier.padding(end = 12.dp),
|
|
||||||
)
|
)
|
||||||
ThemeColorPreference(
|
ThemeColorPreference(
|
||||||
title = "On Tertiary Container",
|
title = "On Tertiary Container",
|
||||||
@ -657,14 +643,17 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
defaultValue = selectedDefaultScheme.onTertiaryContainer,
|
defaultValue = selectedDefaultScheme.onTertiaryContainer,
|
||||||
modifier = Modifier.padding(end = 12.dp),
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
|
ShapedLauncherIcon(
|
||||||
|
badge = { Badge() },
|
||||||
|
size = 48.dp,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
ThemePreferenceCategory(
|
ColorSchemePreferenceCategory(
|
||||||
title = "Surface colors",
|
title = "Surface colors",
|
||||||
previewColorScheme = previewColorScheme,
|
previewColorScheme = previewColorScheme,
|
||||||
darkMode = previewDarkTheme,
|
darkMode = previewDarkTheme,
|
||||||
@ -716,7 +705,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
defaultValue = selectedDefaultScheme.surface,
|
defaultValue = selectedDefaultScheme.surface,
|
||||||
modifier = Modifier.padding(end = 12.dp),
|
|
||||||
)
|
)
|
||||||
ThemeColorPreference(
|
ThemeColorPreference(
|
||||||
title = "Surface Bright",
|
title = "Surface Bright",
|
||||||
@ -740,7 +728,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
defaultValue = selectedDefaultScheme.surfaceBright,
|
defaultValue = selectedDefaultScheme.surfaceBright,
|
||||||
modifier = Modifier.padding(end = 12.dp),
|
|
||||||
)
|
)
|
||||||
ThemeColorPreference(
|
ThemeColorPreference(
|
||||||
title = "Surface Tint",
|
title = "Surface Tint",
|
||||||
@ -764,7 +751,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
defaultValue = selectedDefaultScheme.surfaceTint,
|
defaultValue = selectedDefaultScheme.surfaceTint,
|
||||||
modifier = Modifier.padding(end = 12.dp),
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
@ -824,7 +810,7 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
ThemePreferenceCategory(
|
ColorSchemePreferenceCategory(
|
||||||
title = "Surface container colors",
|
title = "Surface container colors",
|
||||||
previewColorScheme = previewColorScheme,
|
previewColorScheme = previewColorScheme,
|
||||||
darkMode = previewDarkTheme,
|
darkMode = previewDarkTheme,
|
||||||
@ -852,7 +838,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
defaultValue = selectedDefaultScheme.surfaceContainerLowest,
|
defaultValue = selectedDefaultScheme.surfaceContainerLowest,
|
||||||
modifier = Modifier.padding(end = 12.dp),
|
|
||||||
)
|
)
|
||||||
ThemeColorPreference(
|
ThemeColorPreference(
|
||||||
title = "Surface Container Low",
|
title = "Surface Container Low",
|
||||||
@ -876,7 +861,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
defaultValue = selectedDefaultScheme.surfaceContainerLow,
|
defaultValue = selectedDefaultScheme.surfaceContainerLow,
|
||||||
modifier = Modifier.padding(end = 12.dp),
|
|
||||||
)
|
)
|
||||||
ThemeColorPreference(
|
ThemeColorPreference(
|
||||||
title = "Surface Container",
|
title = "Surface Container",
|
||||||
@ -900,7 +884,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
defaultValue = selectedDefaultScheme.surfaceContainer,
|
defaultValue = selectedDefaultScheme.surfaceContainer,
|
||||||
modifier = Modifier.padding(end = 12.dp),
|
|
||||||
)
|
)
|
||||||
ThemeColorPreference(
|
ThemeColorPreference(
|
||||||
title = "Surface Container High",
|
title = "Surface Container High",
|
||||||
@ -924,7 +907,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
defaultValue = selectedDefaultScheme.surfaceContainerHigh,
|
defaultValue = selectedDefaultScheme.surfaceContainerHigh,
|
||||||
modifier = Modifier.padding(end = 12.dp),
|
|
||||||
)
|
)
|
||||||
ThemeColorPreference(
|
ThemeColorPreference(
|
||||||
title = "Surface Container Highest",
|
title = "Surface Container Highest",
|
||||||
@ -948,7 +930,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
defaultValue = selectedDefaultScheme.surfaceContainerHighest,
|
defaultValue = selectedDefaultScheme.surfaceContainerHighest,
|
||||||
modifier = Modifier.padding(end = 12.dp),
|
|
||||||
)
|
)
|
||||||
ThemeColorPreference(
|
ThemeColorPreference(
|
||||||
title = "Surface Variant",
|
title = "Surface Variant",
|
||||||
@ -972,7 +953,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
defaultValue = selectedDefaultScheme.surfaceVariant,
|
defaultValue = selectedDefaultScheme.surfaceVariant,
|
||||||
modifier = Modifier.padding(end = 12.dp),
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
@ -994,7 +974,7 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
ThemePreferenceCategory(
|
ColorSchemePreferenceCategory(
|
||||||
title = "Content colors",
|
title = "Content colors",
|
||||||
previewColorScheme = previewColorScheme,
|
previewColorScheme = previewColorScheme,
|
||||||
darkMode = previewDarkTheme,
|
darkMode = previewDarkTheme,
|
||||||
@ -1022,7 +1002,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
defaultValue = selectedDefaultScheme.onSurface,
|
defaultValue = selectedDefaultScheme.onSurface,
|
||||||
modifier = Modifier.padding(end = 12.dp),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -1048,7 +1027,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
defaultValue = selectedDefaultScheme.onSurfaceVariant,
|
defaultValue = selectedDefaultScheme.onSurfaceVariant,
|
||||||
modifier = Modifier.padding(end = 12.dp),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
ThemeColorPreference(
|
ThemeColorPreference(
|
||||||
@ -1073,7 +1051,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
defaultValue = selectedDefaultScheme.onSurfaceVariant,
|
defaultValue = selectedDefaultScheme.onSurfaceVariant,
|
||||||
modifier = Modifier.padding(end = 12.dp),
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
@ -1126,7 +1103,7 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Surface(
|
Surface(
|
||||||
color = MaterialTheme.colorScheme.surface,
|
color = MaterialTheme.colorScheme.surfaceContainer,
|
||||||
shape = MaterialTheme.shapes.extraSmall,
|
shape = MaterialTheme.shapes.extraSmall,
|
||||||
tonalElevation = 3.dp,
|
tonalElevation = 3.dp,
|
||||||
shadowElevation = 3.dp,
|
shadowElevation = 3.dp,
|
||||||
@ -1148,7 +1125,7 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
ThemePreferenceCategory(
|
ColorSchemePreferenceCategory(
|
||||||
title = "Outline colors",
|
title = "Outline colors",
|
||||||
previewColorScheme = previewColorScheme,
|
previewColorScheme = previewColorScheme,
|
||||||
darkMode = previewDarkTheme,
|
darkMode = previewDarkTheme,
|
||||||
@ -1176,7 +1153,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
defaultValue = selectedDefaultScheme.outline,
|
defaultValue = selectedDefaultScheme.outline,
|
||||||
modifier = Modifier.padding(end = 12.dp),
|
|
||||||
)
|
)
|
||||||
ThemeColorPreference(
|
ThemeColorPreference(
|
||||||
title = "Outline Variant",
|
title = "Outline Variant",
|
||||||
@ -1200,7 +1176,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
defaultValue = selectedDefaultScheme.outlineVariant,
|
defaultValue = selectedDefaultScheme.outlineVariant,
|
||||||
modifier = Modifier.padding(end = 12.dp),
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
@ -1240,7 +1215,7 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
ThemePreferenceCategory(
|
ColorSchemePreferenceCategory(
|
||||||
title = "Error colors",
|
title = "Error colors",
|
||||||
previewColorScheme = previewColorScheme,
|
previewColorScheme = previewColorScheme,
|
||||||
darkMode = previewDarkTheme,
|
darkMode = previewDarkTheme,
|
||||||
@ -1268,7 +1243,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
defaultValue = selectedDefaultScheme.error,
|
defaultValue = selectedDefaultScheme.error,
|
||||||
modifier = Modifier.padding(end = 12.dp),
|
|
||||||
)
|
)
|
||||||
ThemeColorPreference(
|
ThemeColorPreference(
|
||||||
title = "On Error",
|
title = "On Error",
|
||||||
@ -1292,7 +1266,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
defaultValue = selectedDefaultScheme.onError,
|
defaultValue = selectedDefaultScheme.onError,
|
||||||
modifier = Modifier.padding(end = 12.dp),
|
|
||||||
)
|
)
|
||||||
ThemeColorPreference(
|
ThemeColorPreference(
|
||||||
title = "Error Container",
|
title = "Error Container",
|
||||||
@ -1316,7 +1289,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
defaultValue = selectedDefaultScheme.errorContainer,
|
defaultValue = selectedDefaultScheme.errorContainer,
|
||||||
modifier = Modifier.padding(end = 12.dp),
|
|
||||||
)
|
)
|
||||||
ThemeColorPreference(
|
ThemeColorPreference(
|
||||||
title = "On Error Container",
|
title = "On Error Container",
|
||||||
@ -1340,7 +1312,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
defaultValue = selectedDefaultScheme.onErrorContainer,
|
defaultValue = selectedDefaultScheme.onErrorContainer,
|
||||||
modifier = Modifier.padding(end = 12.dp),
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
@ -1354,7 +1325,7 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
ThemePreferenceCategory(
|
ColorSchemePreferenceCategory(
|
||||||
title = "Inverse colors",
|
title = "Inverse colors",
|
||||||
previewColorScheme = previewColorScheme,
|
previewColorScheme = previewColorScheme,
|
||||||
darkMode = previewDarkTheme,
|
darkMode = previewDarkTheme,
|
||||||
@ -1382,7 +1353,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
defaultValue = selectedDefaultScheme.inverseSurface,
|
defaultValue = selectedDefaultScheme.inverseSurface,
|
||||||
modifier = Modifier.padding(end = 12.dp),
|
|
||||||
)
|
)
|
||||||
ThemeColorPreference(
|
ThemeColorPreference(
|
||||||
title = "Inverse Surface",
|
title = "Inverse Surface",
|
||||||
@ -1406,7 +1376,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
defaultValue = selectedDefaultScheme.inverseOnSurface,
|
defaultValue = selectedDefaultScheme.inverseOnSurface,
|
||||||
modifier = Modifier.padding(end = 12.dp),
|
|
||||||
)
|
)
|
||||||
ThemeColorPreference(
|
ThemeColorPreference(
|
||||||
title = "Inverse Primary",
|
title = "Inverse Primary",
|
||||||
@ -1430,7 +1399,6 @@ fun ThemeSettingsScreen(themeId: UUID) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
defaultValue = selectedDefaultScheme.inversePrimary,
|
defaultValue = selectedDefaultScheme.inversePrimary,
|
||||||
modifier = Modifier.padding(end = 12.dp),
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
@ -45,7 +45,7 @@ import androidx.compose.ui.res.stringResource
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
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.R
|
||||||
import de.mm20.launcher2.ui.common.ImportThemeSheet
|
import de.mm20.launcher2.ui.common.ImportThemeSheet
|
||||||
import de.mm20.launcher2.ui.component.preferences.Preference
|
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
|
import de.mm20.launcher2.ui.theme.colorscheme.lightColorSchemeOf
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ThemesSettingsScreen() {
|
fun ColorSchemesSettingsScreen() {
|
||||||
val viewModel: ThemesSettingsScreenVM = viewModel()
|
val viewModel: ColorSchemesSettingsScreenVM = viewModel()
|
||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
val selectedTheme by viewModel.selectedTheme.collectAsStateWithLifecycle(null)
|
val selectedTheme by viewModel.selectedColors.collectAsStateWithLifecycle(null)
|
||||||
val themes by viewModel.themes.collectAsStateWithLifecycle(emptyList())
|
val themes by viewModel.colors.collectAsStateWithLifecycle(emptyList())
|
||||||
|
|
||||||
var deleteTheme by remember { mutableStateOf<Theme?>(null) }
|
var deleteColors by remember { mutableStateOf<Colors?>(null) }
|
||||||
|
|
||||||
var importThemeUri by remember { mutableStateOf<Uri?>(null) }
|
var importThemeUri by remember { mutableStateOf<Uri?>(null) }
|
||||||
|
|
||||||
@ -114,7 +114,7 @@ fun ThemesSettingsScreen() {
|
|||||||
},
|
},
|
||||||
text = { Text(stringResource(R.string.edit)) },
|
text = { Text(stringResource(R.string.edit)) },
|
||||||
onClick = {
|
onClick = {
|
||||||
navController?.navigate("settings/appearance/themes/${theme.id}")
|
navController?.navigate("settings/appearance/colors/${theme.id}")
|
||||||
showMenu = false
|
showMenu = false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -146,7 +146,7 @@ fun ThemesSettingsScreen() {
|
|||||||
},
|
},
|
||||||
text = { Text(stringResource(R.string.menu_delete)) },
|
text = { Text(stringResource(R.string.menu_delete)) },
|
||||||
onClick = {
|
onClick = {
|
||||||
deleteTheme = theme
|
deleteColors = theme
|
||||||
showMenu = false
|
showMenu = false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -162,22 +162,22 @@ fun ThemesSettingsScreen() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (deleteTheme != null) {
|
if (deleteColors != null) {
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = { deleteTheme = null },
|
onDismissRequest = { deleteColors = null },
|
||||||
text = {
|
text = {
|
||||||
Text(
|
Text(
|
||||||
stringResource(
|
stringResource(
|
||||||
R.string.confirmation_delete_color_scheme,
|
R.string.confirmation_delete_color_scheme,
|
||||||
deleteTheme!!.name
|
deleteColors!!.name
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
viewModel.delete(deleteTheme!!)
|
viewModel.delete(deleteColors!!)
|
||||||
deleteTheme = null
|
deleteColors = null
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Text(stringResource(android.R.string.ok))
|
Text(stringResource(android.R.string.ok))
|
||||||
@ -185,7 +185,7 @@ fun ThemesSettingsScreen() {
|
|||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = { deleteTheme = null }
|
onClick = { deleteColors = null }
|
||||||
) {
|
) {
|
||||||
Text(stringResource(android.R.string.cancel))
|
Text(stringResource(android.R.string.cancel))
|
||||||
}
|
}
|
||||||
@ -199,9 +199,9 @@ fun ThemesSettingsScreen() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ColorSchemePreview(theme: Theme) {
|
fun ColorSchemePreview(colors: Colors) {
|
||||||
val dark = LocalDarkTheme.current
|
val dark = LocalDarkTheme.current
|
||||||
val scheme = if (dark) darkColorSchemeOf(theme) else lightColorSchemeOf(theme)
|
val scheme = if (dark) darkColorSchemeOf(colors) else lightColorSchemeOf(colors)
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.height(28.dp)
|
.height(28.dp)
|
||||||
@ -6,13 +6,13 @@ import androidx.core.content.FileProvider
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import de.mm20.launcher2.ktx.tryStartActivity
|
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.preferences.ui.UiSettings
|
||||||
import de.mm20.launcher2.themes.BlackAndWhiteThemeId
|
import de.mm20.launcher2.themes.BlackAndWhiteThemeId
|
||||||
import de.mm20.launcher2.themes.DefaultThemeId
|
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.ThemeRepository
|
||||||
import de.mm20.launcher2.themes.toJson
|
import de.mm20.launcher2.themes.toLegacyJson
|
||||||
import de.mm20.launcher2.ui.R
|
import de.mm20.launcher2.ui.R
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
@ -24,49 +24,49 @@ import org.koin.core.component.inject
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
class ThemesSettingsScreenVM : ViewModel(), KoinComponent {
|
class ColorSchemesSettingsScreenVM : ViewModel(), KoinComponent {
|
||||||
|
|
||||||
private val themeRepository: ThemeRepository by inject()
|
private val themeRepository: ThemeRepository by inject()
|
||||||
private val uiSettings: UiSettings by inject()
|
private val uiSettings: UiSettings by inject()
|
||||||
|
|
||||||
val selectedTheme = uiSettings.theme.map {
|
val selectedColors = uiSettings.colors.map {
|
||||||
when(it) {
|
when(it) {
|
||||||
ThemeDescriptor.Default -> DefaultThemeId
|
ColorsDescriptor.Default -> DefaultThemeId
|
||||||
ThemeDescriptor.BlackAndWhite -> BlackAndWhiteThemeId
|
ColorsDescriptor.BlackAndWhite -> BlackAndWhiteThemeId
|
||||||
is ThemeDescriptor.Custom -> UUID.fromString(it.id)
|
is ColorsDescriptor.Custom -> UUID.fromString(it.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val themes: Flow<List<Theme>> = themeRepository.getThemes()
|
val colors: Flow<List<Colors>> = themeRepository.getAllColors()
|
||||||
|
|
||||||
fun getTheme(id: UUID): Flow<Theme?> {
|
fun getTheme(id: UUID): Flow<Colors?> {
|
||||||
return themeRepository.getTheme(id)
|
return themeRepository.getColors(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateTheme(theme: Theme) {
|
fun updateTheme(colors: Colors) {
|
||||||
themeRepository.updateTheme(theme)
|
themeRepository.updateColors(colors)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun selectTheme(theme: Theme) {
|
fun selectTheme(colors: Colors) {
|
||||||
uiSettings.setTheme(when(theme.id) {
|
uiSettings.setColors(when(colors.id) {
|
||||||
DefaultThemeId -> ThemeDescriptor.Default
|
DefaultThemeId -> ColorsDescriptor.Default
|
||||||
BlackAndWhiteThemeId -> ThemeDescriptor.BlackAndWhite
|
BlackAndWhiteThemeId -> ColorsDescriptor.BlackAndWhite
|
||||||
else -> ThemeDescriptor.Custom(theme.id.toString())
|
else -> ColorsDescriptor.Custom(colors.id.toString())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fun duplicate(theme: Theme) {
|
fun duplicate(colors: Colors) {
|
||||||
themeRepository.createTheme(theme.copy(id = UUID.randomUUID()))
|
themeRepository.createColors(colors.copy(id = UUID.randomUUID()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun delete(theme: Theme) {
|
fun delete(colors: Colors) {
|
||||||
themeRepository.deleteTheme(theme)
|
themeRepository.deleteColors(colors)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun exportTheme(context: Context, theme: Theme) {
|
fun exportTheme(context: Context, colors: Colors) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val file = withContext(Dispatchers.IO) {
|
val file = withContext(Dispatchers.IO) {
|
||||||
val file = File(context.cacheDir, "${theme.name}.kvtheme")
|
val file = File(context.cacheDir, "${colors.name}.kvtheme")
|
||||||
file.writeText(theme.toJson())
|
file.writeText(colors.toLegacyJson())
|
||||||
file
|
file
|
||||||
}
|
}
|
||||||
context.tryStartActivity(Intent().apply {
|
context.tryStartActivity(Intent().apply {
|
||||||
@ -82,8 +82,8 @@ class ThemesSettingsScreenVM : ViewModel(), KoinComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun createNew(context: Context) {
|
fun createNew(context: Context) {
|
||||||
themeRepository.createTheme(
|
themeRepository.createColors(
|
||||||
Theme(
|
Colors(
|
||||||
id = UUID.randomUUID(),
|
id = UUID.randomUUID(),
|
||||||
name = context.getString(R.string.new_color_scheme_name)
|
name = context.getString(R.string.new_color_scheme_name)
|
||||||
)
|
)
|
||||||
@ -3,8 +3,10 @@ package de.mm20.launcher2.ui.settings.colorscheme
|
|||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.expandVertically
|
import androidx.compose.animation.expandVertically
|
||||||
import androidx.compose.animation.shrinkVertically
|
import androidx.compose.animation.shrinkVertically
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
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.ButtonDefaults
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
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.Color
|
||||||
import androidx.compose.ui.graphics.toArgb
|
import androidx.compose.ui.graphics.toArgb
|
||||||
import androidx.compose.ui.res.stringResource
|
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 androidx.compose.ui.unit.dp
|
||||||
|
import de.mm20.launcher2.themes.get
|
||||||
import de.mm20.launcher2.ui.R
|
import de.mm20.launcher2.ui.R
|
||||||
import de.mm20.launcher2.ui.component.BottomSheetDialog
|
import de.mm20.launcher2.ui.component.BottomSheetDialog
|
||||||
import de.mm20.launcher2.ui.component.Tooltip
|
import de.mm20.launcher2.ui.component.Tooltip
|
||||||
@ -47,19 +54,25 @@ fun CorePaletteColorPreference(
|
|||||||
) {
|
) {
|
||||||
var showDialog by remember { mutableStateOf(false) }
|
var showDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Tooltip(
|
Row(
|
||||||
tooltipText = title
|
modifier = modifier.fillMaxWidth()
|
||||||
|
.clickable(
|
||||||
|
onClick = { showDialog = true },
|
||||||
|
)
|
||||||
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
ColorSwatch(
|
ColorSwatch(
|
||||||
color = Color(value ?: defaultValue),
|
color = Color(value ?: defaultValue),
|
||||||
modifier = modifier
|
modifier = Modifier.padding(end = 20.dp).size(48.dp),
|
||||||
.size(48.dp)
|
)
|
||||||
.combinedClickable(
|
|
||||||
onClick = { showDialog = true },
|
Text(
|
||||||
onLongClick = {
|
title,
|
||||||
onValueChange(null)
|
style = MaterialTheme.typography.titleMedium,
|
||||||
}
|
textAlign = TextAlign.Center,
|
||||||
),
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
maxLines = 1,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import androidx.compose.animation.AnimatedContent
|
|||||||
import androidx.compose.foundation.Canvas
|
import androidx.compose.foundation.Canvas
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
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.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.requiredWidth
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.rememberScrollState
|
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.drawscope.Fill
|
||||||
import androidx.compose.ui.graphics.toArgb
|
import androidx.compose.ui.graphics.toArgb
|
||||||
import androidx.compose.ui.res.stringResource
|
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.TextAlign
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import de.mm20.launcher2.themes.ColorRef
|
import de.mm20.launcher2.themes.ColorRef
|
||||||
import de.mm20.launcher2.themes.CorePaletteColor
|
import de.mm20.launcher2.themes.CorePaletteColor
|
||||||
@ -66,7 +70,7 @@ import de.mm20.launcher2.themes.Color as ThemeColor
|
|||||||
@Composable
|
@Composable
|
||||||
fun ThemeColorPreference(
|
fun ThemeColorPreference(
|
||||||
title: String,
|
title: String,
|
||||||
value: de.mm20.launcher2.themes.Color?,
|
value: ThemeColor?,
|
||||||
corePalette: FullCorePalette,
|
corePalette: FullCorePalette,
|
||||||
onValueChange: (ThemeColor?) -> Unit,
|
onValueChange: (ThemeColor?) -> Unit,
|
||||||
defaultValue: ThemeColor,
|
defaultValue: ThemeColor,
|
||||||
@ -74,16 +78,25 @@ fun ThemeColorPreference(
|
|||||||
) {
|
) {
|
||||||
var showDialog by remember { mutableStateOf(false) }
|
var showDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Tooltip(
|
Row(
|
||||||
tooltipText = title
|
modifier = modifier.fillMaxWidth()
|
||||||
|
.clickable(
|
||||||
|
onClick = { showDialog = true },
|
||||||
|
)
|
||||||
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
ColorSwatch(
|
ColorSwatch(
|
||||||
color = Color((value ?: defaultValue).get(corePalette)),
|
color = Color((value ?: defaultValue).get(corePalette)),
|
||||||
modifier = modifier
|
modifier = Modifier.padding(end = 20.dp).size(48.dp),
|
||||||
.size(48.dp)
|
)
|
||||||
.clickable(
|
|
||||||
onClick = { showDialog = true },
|
Text(
|
||||||
),
|
title,
|
||||||
|
style = MaterialTheme.typography.bodyMedium.copy(fontFamily = FontFamily.Monospace),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
maxLines = 1,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,733 @@
|
|||||||
|
package de.mm20.launcher2.ui.settings.shapes
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.horizontalScroll
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.IntrinsicSize
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.layout.wrapContentWidth
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.CutCornerShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.Edit
|
||||||
|
import androidx.compose.material.icons.rounded.OpenInNew
|
||||||
|
import androidx.compose.material.icons.rounded.RestartAlt
|
||||||
|
import androidx.compose.material.icons.rounded.RoundedCorner
|
||||||
|
import androidx.compose.material.icons.rounded.Search
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.BottomSheetDefaults
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.ButtonDefaults
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CardDefaults
|
||||||
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
|
import androidx.compose.material3.FilterChip
|
||||||
|
import androidx.compose.material3.FloatingActionButton
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedTextField
|
||||||
|
import androidx.compose.material3.SegmentedButton
|
||||||
|
import androidx.compose.material3.SegmentedButtonDefaults
|
||||||
|
import androidx.compose.material3.Shapes
|
||||||
|
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
|
||||||
|
import androidx.compose.material3.Slider
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.rotate
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.min
|
||||||
|
import androidx.compose.ui.unit.times
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import de.mm20.launcher2.icons.CutCorner
|
||||||
|
import de.mm20.launcher2.icons.RoundedCornerAlt
|
||||||
|
import de.mm20.launcher2.themes.CornerStyle
|
||||||
|
import de.mm20.launcher2.ui.R
|
||||||
|
import de.mm20.launcher2.ui.component.BottomSheetDialog
|
||||||
|
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
|
||||||
|
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
|
||||||
|
import de.mm20.launcher2.ui.ktx.withCorners
|
||||||
|
import de.mm20.launcher2.ui.theme.shapes.shapesOf
|
||||||
|
import java.util.UUID
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.min
|
||||||
|
import de.mm20.launcher2.themes.Shape as ThemeShape
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ShapeSchemeSettingsScreen(themeId: UUID) {
|
||||||
|
val viewModel: ShapeSchemesSettingsScreenVM = viewModel()
|
||||||
|
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
val theme by remember(
|
||||||
|
viewModel,
|
||||||
|
themeId
|
||||||
|
) { viewModel.getShapes(themeId) }.collectAsStateWithLifecycle(null)
|
||||||
|
val previewShapes = theme?.let { shapesOf(it) }
|
||||||
|
|
||||||
|
|
||||||
|
var editName by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
if (editName) {
|
||||||
|
var name by remember(theme) { mutableStateOf(theme?.name ?: "") }
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { editName = false },
|
||||||
|
text = {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = name,
|
||||||
|
onValueChange = { name = it },
|
||||||
|
singleLine = true
|
||||||
|
)
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
viewModel.updateShapes(theme!!.copy(name = name))
|
||||||
|
editName = false
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.save))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
PreferenceScreen(
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
theme?.name ?: "",
|
||||||
|
modifier = Modifier.clickable {
|
||||||
|
editName = true
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
helpUrl = "https://kvaesitso.mm20.de/docs/user-guide/customization/color-schemes",
|
||||||
|
) {
|
||||||
|
if (theme == null || previewShapes == null) return@PreferenceScreen
|
||||||
|
val baseShape = theme!!.baseShape
|
||||||
|
|
||||||
|
item {
|
||||||
|
PreferenceCategory {
|
||||||
|
ShapePreference(
|
||||||
|
title = stringResource(R.string.preference_shapes_base),
|
||||||
|
shape = baseShape,
|
||||||
|
baseShape = baseShape,
|
||||||
|
factor = 1f,
|
||||||
|
onValueChange = {
|
||||||
|
viewModel.updateShapes(
|
||||||
|
theme!!.copy(
|
||||||
|
baseShape = it ?: ThemeShape(
|
||||||
|
corners = CornerStyle.Rounded,
|
||||||
|
radii = intArrayOf(12, 12, 12, 12)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
titleTextStyle = MaterialTheme.typography.titleMedium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
PreferenceCategory {
|
||||||
|
ShapePreview(
|
||||||
|
previewShapes = previewShapes,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.width(200.dp)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(32.dp)
|
||||||
|
.background(
|
||||||
|
MaterialTheme.colorScheme.surface,
|
||||||
|
MaterialTheme.shapes.extraSmall.withCorners(
|
||||||
|
topStart = false,
|
||||||
|
topEnd = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
Box(
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = 2.dp)
|
||||||
|
.height(32.dp)
|
||||||
|
.background(
|
||||||
|
MaterialTheme.colorScheme.surface,
|
||||||
|
MaterialTheme.shapes.extraSmall.withCorners(
|
||||||
|
bottomStart = false,
|
||||||
|
bottomEnd = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Surface(
|
||||||
|
color = MaterialTheme.colorScheme.surfaceContainer,
|
||||||
|
shape = MaterialTheme.shapes.extraSmall,
|
||||||
|
tonalElevation = 3.dp,
|
||||||
|
shadowElevation = 3.dp,
|
||||||
|
modifier = Modifier.wrapContentWidth()
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(vertical = 8.dp)
|
||||||
|
.width(IntrinsicSize.Max),
|
||||||
|
) {
|
||||||
|
DropdownMenuItem(
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(Icons.Rounded.OpenInNew, null)
|
||||||
|
},
|
||||||
|
text = { Text("Menu") },
|
||||||
|
onClick = { })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ShapePreference(
|
||||||
|
title = "Extra small",
|
||||||
|
shape = theme!!.extraSmall,
|
||||||
|
baseShape = baseShape,
|
||||||
|
factor = 1f / 3f,
|
||||||
|
onValueChange = {
|
||||||
|
viewModel.updateShapes(
|
||||||
|
theme!!.copy(extraSmall = it)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
PreferenceCategory {
|
||||||
|
ShapePreview(
|
||||||
|
previewShapes = previewShapes,
|
||||||
|
) {
|
||||||
|
FilterChip(
|
||||||
|
onClick = {},
|
||||||
|
label = {
|
||||||
|
Text("Chip")
|
||||||
|
},
|
||||||
|
selected = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ShapePreference(
|
||||||
|
title = "Small",
|
||||||
|
shape = theme!!.small,
|
||||||
|
baseShape = baseShape,
|
||||||
|
factor = 2f / 3f,
|
||||||
|
onValueChange = {
|
||||||
|
viewModel.updateShapes(
|
||||||
|
theme!!.copy(small = it)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
PreferenceCategory {
|
||||||
|
ShapePreview(
|
||||||
|
previewShapes = previewShapes,
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.width(200.dp)
|
||||||
|
.height(48.dp)
|
||||||
|
.background(
|
||||||
|
MaterialTheme.colorScheme.surface,
|
||||||
|
MaterialTheme.shapes.medium
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Rounded.Search,
|
||||||
|
null,
|
||||||
|
modifier = Modifier.padding(12.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ShapePreference(
|
||||||
|
title = "Medium",
|
||||||
|
shape = theme!!.medium,
|
||||||
|
baseShape = baseShape,
|
||||||
|
factor = 1f,
|
||||||
|
onValueChange = {
|
||||||
|
viewModel.updateShapes(
|
||||||
|
theme!!.copy(medium = it)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
PreferenceCategory {
|
||||||
|
ShapePreview(
|
||||||
|
previewShapes = previewShapes,
|
||||||
|
) {
|
||||||
|
FloatingActionButton(onClick = {}) {
|
||||||
|
Icon(Icons.Rounded.Edit, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ShapePreference(
|
||||||
|
title = "Large",
|
||||||
|
shape = theme!!.large,
|
||||||
|
baseShape = baseShape,
|
||||||
|
factor = 4f / 3f,
|
||||||
|
onValueChange = {
|
||||||
|
viewModel.updateShapes(
|
||||||
|
theme!!.copy(large = it)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
PreferenceCategory {
|
||||||
|
ShapePreference(
|
||||||
|
title = "Large increased",
|
||||||
|
shape = theme!!.largeIncreased,
|
||||||
|
baseShape = baseShape,
|
||||||
|
factor = 5f / 3f,
|
||||||
|
onValueChange = {
|
||||||
|
viewModel.updateShapes(
|
||||||
|
theme!!.copy(largeIncreased = it)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
PreferenceCategory {
|
||||||
|
ShapePreview(
|
||||||
|
previewShapes = previewShapes,
|
||||||
|
) {
|
||||||
|
Surface(
|
||||||
|
shape = BottomSheetDefaults.ExpandedShape,
|
||||||
|
color = BottomSheetDefaults.ContainerColor,
|
||||||
|
shadowElevation = BottomSheetDefaults.Elevation,
|
||||||
|
tonalElevation = BottomSheetDefaults.Elevation,
|
||||||
|
modifier = Modifier
|
||||||
|
.width(250.dp)
|
||||||
|
.height(144.dp),
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
contentAlignment = Alignment.TopCenter,
|
||||||
|
) {
|
||||||
|
BottomSheetDefaults.DragHandle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ShapePreference(
|
||||||
|
title = "Extra large",
|
||||||
|
shape = theme!!.extraLarge,
|
||||||
|
baseShape = baseShape,
|
||||||
|
factor = 7f / 3f,
|
||||||
|
onValueChange = {
|
||||||
|
viewModel.updateShapes(
|
||||||
|
theme!!.copy(extraLarge = it)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
PreferenceCategory {
|
||||||
|
ShapePreference(
|
||||||
|
title = "Extra large increased",
|
||||||
|
shape = theme!!.extraLargeIncreased,
|
||||||
|
baseShape = baseShape,
|
||||||
|
factor = 8f / 3f,
|
||||||
|
onValueChange = {
|
||||||
|
viewModel.updateShapes(
|
||||||
|
theme!!.copy(extraLargeIncreased = it)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
PreferenceCategory {
|
||||||
|
ShapePreference(
|
||||||
|
title = "Extra extra large",
|
||||||
|
shape = theme!!.extraExtraLarge,
|
||||||
|
baseShape = baseShape,
|
||||||
|
factor = 12f / 3f,
|
||||||
|
onValueChange = {
|
||||||
|
viewModel.updateShapes(
|
||||||
|
theme!!.copy(extraExtraLarge = it)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ShapePreference(
|
||||||
|
title: String,
|
||||||
|
shape: ThemeShape?,
|
||||||
|
baseShape: ThemeShape,
|
||||||
|
factor: Float = 1f,
|
||||||
|
onValueChange: (ThemeShape?) -> Unit,
|
||||||
|
titleTextStyle: TextStyle = MaterialTheme.typography.bodyMedium.copy(fontFamily = FontFamily.Monospace)
|
||||||
|
) {
|
||||||
|
var showDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val f = min(1f, factor)
|
||||||
|
val topStart =
|
||||||
|
(shape?.radii?.get(0)?.div(factor) ?: baseShape.radii?.get(0)?.toFloat() ?: 12f) * f
|
||||||
|
val topEnd =
|
||||||
|
(shape?.radii?.get(1)?.div(factor) ?: baseShape.radii?.get(1)?.toFloat() ?: 12f) * f
|
||||||
|
val bottomEnd =
|
||||||
|
(shape?.radii?.get(2)?.div(factor) ?: baseShape.radii?.get(2)?.toFloat() ?: 12f) * f
|
||||||
|
val bottomStart =
|
||||||
|
(shape?.radii?.get(3)?.div(factor) ?: baseShape.radii?.get(3)?.toFloat() ?: 12f) * f
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 8.dp)
|
||||||
|
.clickable(
|
||||||
|
onClick = { showDialog = true },
|
||||||
|
)
|
||||||
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(end = 20.dp)
|
||||||
|
.size(48.dp),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(min(48.dp, factor * 48.dp))
|
||||||
|
.border(
|
||||||
|
2.dp,
|
||||||
|
MaterialTheme.colorScheme.primary,
|
||||||
|
if ((shape?.corners ?: baseShape.corners
|
||||||
|
?: CornerStyle.Rounded) == CornerStyle.Cut
|
||||||
|
) {
|
||||||
|
CutCornerShape(
|
||||||
|
topStart = topStart.dp,
|
||||||
|
topEnd = topEnd.dp,
|
||||||
|
bottomEnd = bottomEnd.dp,
|
||||||
|
bottomStart = bottomStart.dp
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
RoundedCornerShape(
|
||||||
|
topStart = topStart.dp,
|
||||||
|
topEnd = topEnd.dp,
|
||||||
|
bottomEnd = bottomEnd.dp,
|
||||||
|
bottomStart = bottomStart.dp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style = titleTextStyle,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
maxLines = 1,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showDialog) {
|
||||||
|
val maxRadius = (24 * factor).toInt()
|
||||||
|
|
||||||
|
val baseTopStart = ((baseShape.radii?.get(0) ?: 12) * factor).toInt()
|
||||||
|
val baseTopEnd = ((baseShape.radii?.get(1) ?: 12) * factor).toInt()
|
||||||
|
val baseBottomEnd = ((baseShape.radii?.get(2) ?: 12) * factor).toInt()
|
||||||
|
val baseBottomStart = ((baseShape.radii?.get(3) ?: 12) * factor).toInt()
|
||||||
|
|
||||||
|
|
||||||
|
var currentCornerStyle by remember(shape) { mutableStateOf(shape?.corners) }
|
||||||
|
var currentTopStart by remember(shape) { mutableStateOf(shape?.radii?.get(0)) }
|
||||||
|
var currentTopEnd by remember(shape) { mutableStateOf(shape?.radii?.get(1)) }
|
||||||
|
var currentBottomEnd by remember(shape) { mutableStateOf(shape?.radii?.get(2)) }
|
||||||
|
var currentBottomStart by remember(shape) { mutableStateOf(shape?.radii?.get(3)) }
|
||||||
|
|
||||||
|
val actualCornerStyle = currentCornerStyle ?: baseShape.corners ?: CornerStyle.Rounded
|
||||||
|
val actualTopStart = currentTopStart ?: baseTopStart
|
||||||
|
val actualTopEnd = currentTopEnd ?: baseTopEnd
|
||||||
|
val actualBottomEnd = currentBottomEnd ?: baseBottomEnd
|
||||||
|
val actualBottomStart = currentBottomStart ?: baseBottomStart
|
||||||
|
|
||||||
|
BottomSheetDialog(
|
||||||
|
onDismissRequest = {
|
||||||
|
showDialog = false
|
||||||
|
onValueChange(
|
||||||
|
ThemeShape(
|
||||||
|
corners = currentCornerStyle,
|
||||||
|
radii = if (currentTopStart != null || currentTopEnd != null || currentBottomEnd != null || currentBottomStart != null) {
|
||||||
|
intArrayOf(
|
||||||
|
currentTopStart ?: baseTopStart,
|
||||||
|
currentTopEnd ?: baseTopEnd,
|
||||||
|
currentBottomEnd ?: baseBottomEnd,
|
||||||
|
currentBottomStart ?: baseBottomStart,
|
||||||
|
)
|
||||||
|
} else null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}) {
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
.padding(it),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
) {
|
||||||
|
val previewShape = if ((currentCornerStyle ?: baseShape.corners
|
||||||
|
?: CornerStyle.Rounded) == CornerStyle.Rounded
|
||||||
|
) {
|
||||||
|
RoundedCornerShape(
|
||||||
|
topStart = (currentTopStart ?: baseTopStart).dp,
|
||||||
|
topEnd = (currentTopEnd ?: baseTopEnd).dp,
|
||||||
|
bottomEnd = (currentBottomEnd ?: baseBottomEnd).dp,
|
||||||
|
bottomStart = (currentBottomStart ?: baseBottomStart).dp
|
||||||
|
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
CutCornerShape(
|
||||||
|
topStart = (currentTopStart ?: baseTopStart).dp,
|
||||||
|
topEnd = (currentTopEnd ?: baseTopEnd).dp,
|
||||||
|
bottomEnd = (currentBottomEnd ?: baseBottomEnd).dp,
|
||||||
|
bottomStart = (currentBottomStart ?: baseBottomStart).dp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(max(96, maxRadius * 2).dp)
|
||||||
|
.background(MaterialTheme.colorScheme.surfaceContainer, previewShape)
|
||||||
|
.border(
|
||||||
|
2.dp,
|
||||||
|
MaterialTheme.colorScheme.primary,
|
||||||
|
previewShape
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
SingleChoiceSegmentedButtonRow(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
SegmentedButton(
|
||||||
|
selected = actualCornerStyle == CornerStyle.Rounded,
|
||||||
|
onClick = {
|
||||||
|
currentCornerStyle = CornerStyle.Rounded
|
||||||
|
},
|
||||||
|
shape = SegmentedButtonDefaults.itemShape(0, 2),
|
||||||
|
icon = {
|
||||||
|
SegmentedButtonDefaults.Icon(
|
||||||
|
active = actualCornerStyle == CornerStyle.Rounded,
|
||||||
|
) {
|
||||||
|
Icon(Icons.Rounded.RoundedCornerAlt, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.preference_cards_shape_rounded))
|
||||||
|
}
|
||||||
|
SegmentedButton(
|
||||||
|
selected = actualCornerStyle == CornerStyle.Cut,
|
||||||
|
onClick = {
|
||||||
|
currentCornerStyle = CornerStyle.Cut
|
||||||
|
},
|
||||||
|
shape = SegmentedButtonDefaults.itemShape(1, 2),
|
||||||
|
icon = {
|
||||||
|
SegmentedButtonDefaults.Icon(
|
||||||
|
active = actualCornerStyle == CornerStyle.Cut,
|
||||||
|
) {
|
||||||
|
Icon(Icons.Rounded.CutCorner, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.preference_cards_shape_cut))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Rounded.RoundedCorner,
|
||||||
|
null,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(end = 8.dp)
|
||||||
|
.rotate(-90f)
|
||||||
|
)
|
||||||
|
Slider(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f),
|
||||||
|
value = actualTopStart.toFloat(),
|
||||||
|
onValueChange = {
|
||||||
|
currentTopStart = it.toInt()
|
||||||
|
},
|
||||||
|
valueRange = 0f..maxRadius.toFloat(),
|
||||||
|
steps = maxRadius + 1
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = actualTopStart.toString(),
|
||||||
|
modifier = Modifier.width(32.dp),
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Rounded.RoundedCorner,
|
||||||
|
null,
|
||||||
|
modifier = Modifier.padding(end = 8.dp)
|
||||||
|
)
|
||||||
|
Slider(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f),
|
||||||
|
value = actualTopEnd.toFloat(),
|
||||||
|
onValueChange = {
|
||||||
|
currentTopEnd = it.toInt()
|
||||||
|
},
|
||||||
|
valueRange = 0f..maxRadius.toFloat(),
|
||||||
|
steps = maxRadius + 1,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = actualTopEnd.toString(),
|
||||||
|
modifier = Modifier.width(32.dp),
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Rounded.RoundedCorner,
|
||||||
|
null,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(end = 8.dp)
|
||||||
|
.rotate(90f)
|
||||||
|
)
|
||||||
|
Slider(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f),
|
||||||
|
value = actualBottomEnd.toFloat(),
|
||||||
|
onValueChange = {
|
||||||
|
currentBottomEnd = it.toInt()
|
||||||
|
},
|
||||||
|
valueRange = 0f..maxRadius.toFloat(),
|
||||||
|
steps = maxRadius + 1,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = actualBottomEnd.toString(),
|
||||||
|
modifier = Modifier.width(32.dp),
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Rounded.RoundedCorner,
|
||||||
|
null,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(end = 8.dp)
|
||||||
|
.rotate(180f)
|
||||||
|
)
|
||||||
|
Slider(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f),
|
||||||
|
value = actualBottomStart.toFloat(),
|
||||||
|
onValueChange = {
|
||||||
|
currentBottomStart = it.toInt()
|
||||||
|
},
|
||||||
|
valueRange = 0f..maxRadius.toFloat(),
|
||||||
|
steps = maxRadius + 1,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = actualBottomStart.toString(),
|
||||||
|
modifier = Modifier.width(32.dp),
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
HorizontalDivider(
|
||||||
|
modifier = Modifier.padding(top = 16.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
TextButton(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 8.dp)
|
||||||
|
.align(Alignment.End),
|
||||||
|
contentPadding = ButtonDefaults.TextButtonWithIconContentPadding,
|
||||||
|
onClick = {
|
||||||
|
currentTopStart = null
|
||||||
|
currentTopEnd = null
|
||||||
|
currentBottomEnd = null
|
||||||
|
currentBottomStart = null
|
||||||
|
currentCornerStyle = null
|
||||||
|
onValueChange(null)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Rounded.RestartAlt, null,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(ButtonDefaults.IconSpacing)
|
||||||
|
.size(ButtonDefaults.IconSize)
|
||||||
|
)
|
||||||
|
Text(stringResource(R.string.preference_restore_default))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ShapePreview(
|
||||||
|
previewShapes: Shapes,
|
||||||
|
content: @Composable () -> Unit,
|
||||||
|
) {
|
||||||
|
MaterialTheme(
|
||||||
|
shapes = previewShapes
|
||||||
|
) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp),
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surfaceContainer
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.horizontalScroll(rememberScrollState())
|
||||||
|
.padding(16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
) {
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,204 @@
|
|||||||
|
package de.mm20.launcher2.ui.settings.shapes
|
||||||
|
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.shape.CutCornerShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.Add
|
||||||
|
import androidx.compose.material.icons.rounded.ContentCopy
|
||||||
|
import androidx.compose.material.icons.rounded.Delete
|
||||||
|
import androidx.compose.material.icons.rounded.Edit
|
||||||
|
import androidx.compose.material.icons.rounded.MoreVert
|
||||||
|
import androidx.compose.material.icons.rounded.RadioButtonChecked
|
||||||
|
import androidx.compose.material.icons.rounded.RadioButtonUnchecked
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.DropdownMenu
|
||||||
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
|
import androidx.compose.material3.FloatingActionButton
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import de.mm20.launcher2.themes.CornerStyle
|
||||||
|
import de.mm20.launcher2.themes.Shapes
|
||||||
|
import de.mm20.launcher2.ui.R
|
||||||
|
import de.mm20.launcher2.ui.component.preferences.Preference
|
||||||
|
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
|
||||||
|
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
|
||||||
|
import de.mm20.launcher2.ui.locals.LocalNavController
|
||||||
|
import de.mm20.launcher2.ui.theme.shapes.shapesOf
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ShapeSchemesSettingsScreen() {
|
||||||
|
val viewModel: ShapeSchemesSettingsScreenVM = viewModel()
|
||||||
|
val navController = LocalNavController.current
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
val selectedTheme by viewModel.selectedShapes.collectAsStateWithLifecycle(null)
|
||||||
|
val themes by viewModel.shapes.collectAsStateWithLifecycle(emptyList())
|
||||||
|
|
||||||
|
var deleteShapes by remember { mutableStateOf<Shapes?>(null) }
|
||||||
|
|
||||||
|
PreferenceScreen(
|
||||||
|
title = stringResource(R.string.preference_screen_shapes),
|
||||||
|
floatingActionButton = {
|
||||||
|
FloatingActionButton(onClick = { viewModel.createNew(context) }) {
|
||||||
|
Icon(Icons.Rounded.Add, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
PreferenceCategory {
|
||||||
|
for (theme in themes) {
|
||||||
|
var showMenu by remember { mutableStateOf(false) }
|
||||||
|
Preference(
|
||||||
|
icon = if (theme.id == selectedTheme) Icons.Rounded.RadioButtonChecked else Icons.Rounded.RadioButtonUnchecked,
|
||||||
|
title = theme.name,
|
||||||
|
controls = {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
ShapesPreview(theme)
|
||||||
|
IconButton(
|
||||||
|
modifier = Modifier.padding(start = 12.dp),
|
||||||
|
onClick = { showMenu = true }) {
|
||||||
|
Icon(Icons.Rounded.MoreVert, null)
|
||||||
|
}
|
||||||
|
DropdownMenu(
|
||||||
|
expanded = showMenu,
|
||||||
|
onDismissRequest = { showMenu = false }
|
||||||
|
) {
|
||||||
|
if (!theme.builtIn) {
|
||||||
|
DropdownMenuItem(
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(Icons.Rounded.Edit, null)
|
||||||
|
},
|
||||||
|
text = { Text(stringResource(R.string.edit)) },
|
||||||
|
onClick = {
|
||||||
|
navController?.navigate("settings/appearance/shapes/${theme.id}")
|
||||||
|
showMenu = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
DropdownMenuItem(
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(Icons.Rounded.ContentCopy, null)
|
||||||
|
},
|
||||||
|
text = { Text(stringResource(R.string.duplicate)) },
|
||||||
|
onClick = {
|
||||||
|
viewModel.duplicate(theme)
|
||||||
|
showMenu = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if (!theme.builtIn) {
|
||||||
|
DropdownMenuItem(
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(Icons.Rounded.Delete, null)
|
||||||
|
},
|
||||||
|
text = { Text(stringResource(R.string.menu_delete)) },
|
||||||
|
onClick = {
|
||||||
|
deleteShapes = theme
|
||||||
|
showMenu = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
viewModel.selectShapes(theme)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (deleteShapes != null) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { deleteShapes = null },
|
||||||
|
text = {
|
||||||
|
Text(
|
||||||
|
stringResource(
|
||||||
|
R.string.confirmation_delete_shapes_scheme,
|
||||||
|
deleteShapes!!.name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
viewModel.delete(deleteShapes!!)
|
||||||
|
deleteShapes = null
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(stringResource(android.R.string.ok))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = { deleteShapes = null }
|
||||||
|
) {
|
||||||
|
Text(stringResource(android.R.string.cancel))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ShapesPreview(theme: Shapes) {
|
||||||
|
val shape = theme.medium
|
||||||
|
val baseShape = theme.baseShape
|
||||||
|
|
||||||
|
val topStart =
|
||||||
|
(shape?.radii?.get(0)?.toFloat() ?: baseShape.radii?.get(0)?.toFloat() ?: 8f) / 3f * 2f
|
||||||
|
val topEnd =
|
||||||
|
(shape?.radii?.get(1)?.toFloat() ?: baseShape.radii?.get(1)?.toFloat() ?: 8f) / 3f * 2f
|
||||||
|
val bottomEnd =
|
||||||
|
(shape?.radii?.get(2)?.toFloat() ?: baseShape.radii?.get(2)?.toFloat() ?: 8f) / 3f * 2f
|
||||||
|
val bottomStart =
|
||||||
|
(shape?.radii?.get(3)?.toFloat() ?: baseShape.radii?.get(3)?.toFloat() ?: 8f) / 3f * 2f
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(32.dp)
|
||||||
|
.border(
|
||||||
|
2.dp,
|
||||||
|
MaterialTheme.colorScheme.primary,
|
||||||
|
if ((theme.medium?.corners ?: theme.baseShape.corners
|
||||||
|
?: CornerStyle.Rounded) == CornerStyle.Cut
|
||||||
|
) {
|
||||||
|
CutCornerShape(
|
||||||
|
topStart = topStart.dp,
|
||||||
|
topEnd = topEnd.dp,
|
||||||
|
bottomEnd = bottomEnd.dp,
|
||||||
|
bottomStart = bottomStart.dp
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
RoundedCornerShape(
|
||||||
|
topStart = topStart.dp,
|
||||||
|
topEnd = topEnd.dp,
|
||||||
|
bottomEnd = bottomEnd.dp,
|
||||||
|
bottomStart = bottomStart.dp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,68 @@
|
|||||||
|
package de.mm20.launcher2.ui.settings.shapes
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import de.mm20.launcher2.preferences.ShapesDescriptor
|
||||||
|
import de.mm20.launcher2.preferences.ui.UiSettings
|
||||||
|
import de.mm20.launcher2.themes.CutShapesId
|
||||||
|
import de.mm20.launcher2.themes.DefaultThemeId
|
||||||
|
import de.mm20.launcher2.themes.ExtraRoundShapesId
|
||||||
|
import de.mm20.launcher2.themes.RectShapesId
|
||||||
|
import de.mm20.launcher2.themes.Shapes
|
||||||
|
import de.mm20.launcher2.themes.ThemeRepository
|
||||||
|
import de.mm20.launcher2.ui.R
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import org.koin.core.component.KoinComponent
|
||||||
|
import org.koin.core.component.inject
|
||||||
|
import java.util.UUID
|
||||||
|
import kotlin.getValue
|
||||||
|
|
||||||
|
class ShapeSchemesSettingsScreenVM : ViewModel(), KoinComponent {
|
||||||
|
|
||||||
|
private val themeRepository: ThemeRepository by inject()
|
||||||
|
private val uiSettings: UiSettings by inject()
|
||||||
|
|
||||||
|
val selectedShapes = uiSettings.shapes.map {
|
||||||
|
when(it) {
|
||||||
|
ShapesDescriptor.Default -> DefaultThemeId
|
||||||
|
ShapesDescriptor.Cut -> CutShapesId
|
||||||
|
ShapesDescriptor.ExtraRound -> ExtraRoundShapesId
|
||||||
|
ShapesDescriptor.Rect -> RectShapesId
|
||||||
|
is ShapesDescriptor.Custom -> UUID.fromString(it.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val shapes: Flow<List<Shapes>> = themeRepository.getAllShapes()
|
||||||
|
|
||||||
|
fun getShapes(id: UUID): Flow<Shapes?> {
|
||||||
|
return themeRepository.getShapes(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateShapes(shapes: Shapes) {
|
||||||
|
themeRepository.updateShapes(shapes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun selectShapes(shapes: Shapes) {
|
||||||
|
uiSettings.setShapes(when(shapes.id) {
|
||||||
|
DefaultThemeId -> ShapesDescriptor.Default
|
||||||
|
else -> ShapesDescriptor.Custom(shapes.id.toString())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fun duplicate(shapes: Shapes) {
|
||||||
|
themeRepository.createShapes(shapes.copy(id = UUID.randomUUID()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun delete(shapes: Shapes) {
|
||||||
|
themeRepository.deleteShapes(shapes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createNew(context: Context) {
|
||||||
|
themeRepository.createShapes(
|
||||||
|
Shapes(
|
||||||
|
id = UUID.randomUUID(),
|
||||||
|
name = context.getString(R.string.new_shapes_name)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -16,6 +16,7 @@ import de.mm20.launcher2.preferences.ui.UiSettings
|
|||||||
import de.mm20.launcher2.themes.ThemeRepository
|
import de.mm20.launcher2.themes.ThemeRepository
|
||||||
import de.mm20.launcher2.ui.locals.LocalDarkTheme
|
import de.mm20.launcher2.ui.locals.LocalDarkTheme
|
||||||
import de.mm20.launcher2.ui.theme.colorscheme.*
|
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.DefaultTypography
|
||||||
import de.mm20.launcher2.ui.theme.typography.getDeviceDefaultTypography
|
import de.mm20.launcher2.ui.theme.typography.getDeviceDefaultTypography
|
||||||
import kotlinx.coroutines.flow.flatMapLatest
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
@ -33,11 +34,17 @@ fun LauncherTheme(
|
|||||||
val uiSettings: UiSettings = koinInject()
|
val uiSettings: UiSettings = koinInject()
|
||||||
val themeRepository: ThemeRepository = koinInject()
|
val themeRepository: ThemeRepository = koinInject()
|
||||||
|
|
||||||
val theme by remember {
|
val themeColors by remember {
|
||||||
uiSettings.theme.flatMapLatest {
|
uiSettings.colors.flatMapLatest {
|
||||||
themeRepository.getThemeOrDefault(it)
|
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(
|
val colorSchemePref by remember { uiSettings.colorScheme }.collectAsState(
|
||||||
ColorSchemePref.System
|
ColorSchemePref.System
|
||||||
@ -45,27 +52,20 @@ fun LauncherTheme(
|
|||||||
val darkTheme =
|
val darkTheme =
|
||||||
colorSchemePref == ColorSchemePref.Dark || colorSchemePref == ColorSchemePref.System && isSystemInDarkTheme()
|
colorSchemePref == ColorSchemePref.Dark || colorSchemePref == ColorSchemePref.System && isSystemInDarkTheme()
|
||||||
|
|
||||||
val cornerRadius by remember {
|
if (themeColors == null || themeShapes == null) {
|
||||||
uiSettings.cardStyle.map {
|
return
|
||||||
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))
|
|
||||||
|
|
||||||
val colorScheme = if (darkTheme) {
|
val colorScheme = if (darkTheme) {
|
||||||
darkColorSchemeOf(theme)
|
darkColorSchemeOf(themeColors!!)
|
||||||
} else {
|
} else {
|
||||||
lightColorSchemeOf(theme)
|
lightColorSchemeOf(themeColors!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val shapes = shapesOf(themeShapes!!)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
val font by remember { uiSettings.font }.collectAsState(
|
val font by remember { uiSettings.font }.collectAsState(
|
||||||
Font.Outfit
|
Font.Outfit
|
||||||
)
|
)
|
||||||
@ -80,13 +80,7 @@ fun LauncherTheme(
|
|||||||
MaterialExpressiveTheme(
|
MaterialExpressiveTheme(
|
||||||
colorScheme = colorScheme,
|
colorScheme = colorScheme,
|
||||||
typography = typography,
|
typography = typography,
|
||||||
shapes = Shapes(
|
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))),
|
|
||||||
),
|
|
||||||
content = content
|
content = content
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,83 +0,0 @@
|
|||||||
package de.mm20.launcher2.ui.theme.colorscheme
|
|
||||||
|
|
||||||
import androidx.compose.material3.darkColorScheme
|
|
||||||
import androidx.compose.material3.lightColorScheme
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
|
|
||||||
|
|
||||||
val LightBlackAndWhiteColorScheme = lightColorScheme(
|
|
||||||
primary = Color.Black,
|
|
||||||
onPrimary = Color.White,
|
|
||||||
primaryContainer = Color.White,
|
|
||||||
onPrimaryContainer = Color.Black,
|
|
||||||
inversePrimary = Color.White,
|
|
||||||
secondary = Color.Black,
|
|
||||||
onSecondary = Color.White,
|
|
||||||
secondaryContainer = Color.White,
|
|
||||||
onSecondaryContainer = Color.Black,
|
|
||||||
tertiary = Color.Black,
|
|
||||||
onTertiary = Color.White,
|
|
||||||
tertiaryContainer = Color.White,
|
|
||||||
onTertiaryContainer = Color.Black,
|
|
||||||
background = Color.White,
|
|
||||||
onBackground = Color.Black,
|
|
||||||
surface = Color.White,
|
|
||||||
onSurface = Color.Black,
|
|
||||||
surfaceVariant = Color.White,
|
|
||||||
onSurfaceVariant = Color.Black,
|
|
||||||
inverseSurface = Color.Black,
|
|
||||||
inverseOnSurface = Color.White,
|
|
||||||
error = Color(0xFFC10000),
|
|
||||||
onError = Color(0xFFFFFFFF),
|
|
||||||
errorContainer = Color(0xFFFFDAD3),
|
|
||||||
onErrorContainer = Color(0xFF410000),
|
|
||||||
outline = Color.Black,
|
|
||||||
surfaceTint = Color.White,
|
|
||||||
outlineVariant = Color.Black,
|
|
||||||
scrim = Color.Black,
|
|
||||||
surfaceDim = Color.White,
|
|
||||||
surfaceBright = Color.White,
|
|
||||||
surfaceContainer = Color.White,
|
|
||||||
surfaceContainerHigh = Color.White,
|
|
||||||
surfaceContainerHighest = Color.White,
|
|
||||||
surfaceContainerLow = Color.White,
|
|
||||||
surfaceContainerLowest = Color.White,
|
|
||||||
)
|
|
||||||
val DarkBlackAndWhiteColorScheme = darkColorScheme(
|
|
||||||
primary = Color.White,
|
|
||||||
onPrimary = Color.Black,
|
|
||||||
primaryContainer = Color.Black,
|
|
||||||
onPrimaryContainer = Color.White,
|
|
||||||
inversePrimary = Color.Black,
|
|
||||||
secondary = Color.White,
|
|
||||||
onSecondary = Color.Black,
|
|
||||||
secondaryContainer = Color.Black,
|
|
||||||
onSecondaryContainer = Color.White,
|
|
||||||
tertiary = Color.White,
|
|
||||||
onTertiary = Color.Black,
|
|
||||||
tertiaryContainer = Color.Black,
|
|
||||||
onTertiaryContainer = Color.White,
|
|
||||||
background = Color.Black,
|
|
||||||
onBackground = Color.White,
|
|
||||||
surface = Color.Black,
|
|
||||||
onSurface = Color.White,
|
|
||||||
surfaceVariant = Color.Black,
|
|
||||||
onSurfaceVariant = Color.White,
|
|
||||||
inverseSurface = Color.White,
|
|
||||||
inverseOnSurface = Color.Black,
|
|
||||||
error = Color(0xffffb4a6),
|
|
||||||
onError = Color(0xff690000),
|
|
||||||
errorContainer = Color(0xff940000),
|
|
||||||
onErrorContainer = Color(0xffffb4a6),
|
|
||||||
outline = Color.White,
|
|
||||||
surfaceTint = Color.White,
|
|
||||||
outlineVariant = Color.White,
|
|
||||||
scrim = Color.White,
|
|
||||||
surfaceDim = Color.Black,
|
|
||||||
surfaceBright = Color.Black,
|
|
||||||
surfaceContainer = Color.Black,
|
|
||||||
surfaceContainerHigh = Color.Black,
|
|
||||||
surfaceContainerHighest = Color.Black,
|
|
||||||
surfaceContainerLow = Color.Black,
|
|
||||||
surfaceContainerLowest = Color.Black,
|
|
||||||
)
|
|
||||||
@ -16,20 +16,20 @@ import de.mm20.launcher2.themes.DefaultDarkColorScheme
|
|||||||
import de.mm20.launcher2.themes.DefaultLightColorScheme
|
import de.mm20.launcher2.themes.DefaultLightColorScheme
|
||||||
import de.mm20.launcher2.themes.FullColorScheme
|
import de.mm20.launcher2.themes.FullColorScheme
|
||||||
import de.mm20.launcher2.themes.PartialCorePalette
|
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.get
|
||||||
import de.mm20.launcher2.themes.merge
|
import de.mm20.launcher2.themes.merge
|
||||||
import de.mm20.launcher2.ui.locals.LocalWallpaperColors
|
import de.mm20.launcher2.ui.locals.LocalWallpaperColors
|
||||||
import org.koin.compose.koinInject
|
import org.koin.compose.koinInject
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun lightColorSchemeOf(theme: Theme): ColorScheme {
|
fun lightColorSchemeOf(colors: ThemeColors): ColorScheme {
|
||||||
return colorSchemeOf(theme.lightColorScheme.merge(DefaultLightColorScheme), theme.corePalette)
|
return colorSchemeOf(colors.lightColorScheme.merge(DefaultLightColorScheme), colors.corePalette)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun darkColorSchemeOf(theme: Theme): ColorScheme {
|
fun darkColorSchemeOf(colors: ThemeColors): ColorScheme {
|
||||||
return colorSchemeOf(theme.darkColorScheme.merge(DefaultDarkColorScheme), theme.corePalette)
|
return colorSchemeOf(colors.darkColorScheme.merge(DefaultDarkColorScheme), colors.corePalette)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -1,67 +0,0 @@
|
|||||||
package de.mm20.launcher2.ui.theme.colorscheme
|
|
||||||
|
|
||||||
import androidx.compose.material3.darkColorScheme
|
|
||||||
import androidx.compose.material3.lightColorScheme
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
|
|
||||||
val LightDefaultColorScheme = lightColorScheme(
|
|
||||||
primary = Color(0xFF3D608A),
|
|
||||||
onPrimary = Color(0xFFFFFFFF),
|
|
||||||
primaryContainer = Color(0xFFD2E4FF),
|
|
||||||
onPrimaryContainer = Color(0xFF001C37),
|
|
||||||
inversePrimary = Color(0xFFA5C9F8),
|
|
||||||
secondary = Color(0xFF535F70),
|
|
||||||
onSecondary = Color(0xFFFFFFFF),
|
|
||||||
secondaryContainer = Color(0xFFD7E3F8),
|
|
||||||
onSecondaryContainer = Color(0xFF101C2B),
|
|
||||||
tertiary = Color(0xFF6B5778),
|
|
||||||
onTertiary = Color(0xFFFFFFFF),
|
|
||||||
tertiaryContainer = Color(0xFFF3DAFF),
|
|
||||||
onTertiaryContainer = Color(0xFF251431),
|
|
||||||
surface = Color(0xFFFAF9FC),
|
|
||||||
onSurface = Color(0xFF1A1C1E),
|
|
||||||
onSurfaceVariant = Color(0xFF43474E),
|
|
||||||
inverseSurface = Color(0xFF2F3033),
|
|
||||||
inverseOnSurface = Color(0xFFF1F0F3),
|
|
||||||
error = Color(0xFFBA1A1A),
|
|
||||||
onError = Color(0xFFFFFFFF),
|
|
||||||
errorContainer = Color(0xFFFFDAD5),
|
|
||||||
onErrorContainer = Color(0xFF410002),
|
|
||||||
outline = Color(0xFF73777F),
|
|
||||||
outlineVariant = Color(0xFFC3C6CF),
|
|
||||||
scrim = Color(0xFF000000),
|
|
||||||
background = Color(0xFFFDFCFF),
|
|
||||||
onBackground = Color(0xFF1A1C1E),
|
|
||||||
surfaceVariant = Color(0xFFDFE2EB),
|
|
||||||
)
|
|
||||||
|
|
||||||
val DarkDefaultColorScheme = darkColorScheme(
|
|
||||||
primary = Color(0xFFA5C9F8),
|
|
||||||
onPrimary = Color(0xFF023258),
|
|
||||||
primaryContainer = Color(0xFF224970),
|
|
||||||
onPrimaryContainer = Color(0xFFD2E4FF),
|
|
||||||
inversePrimary = Color(0xFF3D608A),
|
|
||||||
secondary = Color(0xFFBBC7DB),
|
|
||||||
onSecondary = Color(0xFF253141),
|
|
||||||
secondaryContainer = Color(0xFF3C4858),
|
|
||||||
onSecondaryContainer = Color(0xFFD7E3F8),
|
|
||||||
tertiary = Color(0xFFD6BEE4),
|
|
||||||
onTertiary = Color(0xFF3B2947),
|
|
||||||
tertiaryContainer = Color(0xFF523F5F),
|
|
||||||
onTertiaryContainer = Color(0xFFF3DAFF),
|
|
||||||
surface = Color(0xFF1A1C1E),
|
|
||||||
onSurface = Color(0xFFE3E2E5),
|
|
||||||
onSurfaceVariant = Color(0xFFC3C6CF),
|
|
||||||
inverseSurface = Color(0xFFE3E2E5),
|
|
||||||
inverseOnSurface = Color(0xFF2F3033),
|
|
||||||
error = Color(0xFFFFB4AB),
|
|
||||||
onError = Color(0xFF690004),
|
|
||||||
errorContainer = Color(0xFF930009),
|
|
||||||
onErrorContainer = Color(0xFFFFB4AB),
|
|
||||||
outline = Color(0xFF8D9199),
|
|
||||||
outlineVariant = Color(0xFF43474E),
|
|
||||||
scrim = Color(0xFF000000),
|
|
||||||
background = Color(0xFF1A1C1E),
|
|
||||||
onBackground = Color(0xFFE3E2E5),
|
|
||||||
surfaceVariant = Color(0xFF43474E),
|
|
||||||
)
|
|
||||||
@ -1,67 +0,0 @@
|
|||||||
package de.mm20.launcher2.ui.theme.colorscheme
|
|
||||||
|
|
||||||
import androidx.compose.material3.darkColorScheme
|
|
||||||
import androidx.compose.material3.lightColorScheme
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
|
|
||||||
val LightEasterEggColorScheme = lightColorScheme(
|
|
||||||
primary = Color(0xFFB40180),
|
|
||||||
onPrimary = Color(0xFFFFFFFF),
|
|
||||||
primaryContainer = Color(0xFFFFD8E9),
|
|
||||||
onPrimaryContainer = Color(0xFF3C0029),
|
|
||||||
inversePrimary = Color(0xFFFFAFD7),
|
|
||||||
secondary = Color(0xFF725763),
|
|
||||||
onSecondary = Color(0xFFFFFFFF),
|
|
||||||
secondaryContainer = Color(0xFFFDD9E7),
|
|
||||||
onSecondaryContainer = Color(0xFF29151F),
|
|
||||||
tertiary = Color(0xFF7F543B),
|
|
||||||
onTertiary = Color(0xFFFFFFFF),
|
|
||||||
tertiaryContainer = Color(0xFFFFDBC9),
|
|
||||||
onTertiaryContainer = Color(0xFF311302),
|
|
||||||
surface = Color(0xFFFCF8FC),
|
|
||||||
onSurface = Color(0xFF1C1B1E),
|
|
||||||
onSurfaceVariant = Color(0xFF4F4448),
|
|
||||||
inverseSurface = Color(0xFF313033),
|
|
||||||
inverseOnSurface = Color(0xFFF3EFF3),
|
|
||||||
error = Color(0xFFBA1A1A),
|
|
||||||
onError = Color(0xFFFFFFFF),
|
|
||||||
errorContainer = Color(0xFFFFDAD5),
|
|
||||||
onErrorContainer = Color(0xFF410002),
|
|
||||||
outline = Color(0xFF817379),
|
|
||||||
outlineVariant = Color(0xFFD3C2C8),
|
|
||||||
scrim = Color(0xFF000000),
|
|
||||||
background = Color(0xFFFFFBFF),
|
|
||||||
onBackground = Color(0xFF1C1B1E),
|
|
||||||
surfaceVariant = Color(0xFFF0DEE4),
|
|
||||||
)
|
|
||||||
|
|
||||||
val DarkEasterEggColorScheme = darkColorScheme(
|
|
||||||
primary = Color(0xFFFFAFD7),
|
|
||||||
onPrimary = Color(0xFF620044),
|
|
||||||
primaryContainer = Color(0xFF8A0061),
|
|
||||||
onPrimaryContainer = Color(0xFFFFD8E9),
|
|
||||||
inversePrimary = Color(0xFFB40180),
|
|
||||||
secondary = Color(0xFFE0BDCB),
|
|
||||||
onSecondary = Color(0xFF402A35),
|
|
||||||
secondaryContainer = Color(0xFF59404B),
|
|
||||||
onSecondaryContainer = Color(0xFFFDD9E7),
|
|
||||||
tertiary = Color(0xFFF3BA9B),
|
|
||||||
onTertiary = Color(0xFF4A2812),
|
|
||||||
tertiaryContainer = Color(0xFF643D26),
|
|
||||||
onTertiaryContainer = Color(0xFFFFDBC9),
|
|
||||||
surface = Color(0xFF1C1B1E),
|
|
||||||
onSurface = Color(0xFFE5E1E5),
|
|
||||||
onSurfaceVariant = Color(0xFFD3C2C8),
|
|
||||||
inverseSurface = Color(0xFFE5E1E5),
|
|
||||||
inverseOnSurface = Color(0xFF313033),
|
|
||||||
error = Color(0xFFFFB4AB),
|
|
||||||
onError = Color(0xFF690004),
|
|
||||||
errorContainer = Color(0xFF930009),
|
|
||||||
onErrorContainer = Color(0xFFFFB4AB),
|
|
||||||
outline = Color(0xFF9C8D92),
|
|
||||||
outlineVariant = Color(0xFF4F4448),
|
|
||||||
scrim = Color(0xFF000000),
|
|
||||||
background = Color(0xFF1C1B1E),
|
|
||||||
onBackground = Color(0xFFE5E1E5),
|
|
||||||
surfaceVariant = Color(0xFF4F4448),
|
|
||||||
)
|
|
||||||
@ -1,56 +0,0 @@
|
|||||||
package de.mm20.launcher2.ui.theme.colorscheme
|
|
||||||
|
|
||||||
import androidx.compose.material3.ColorScheme
|
|
||||||
import androidx.compose.material3.darkColorScheme
|
|
||||||
import androidx.compose.material3.lightColorScheme
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.graphics.toArgb
|
|
||||||
import de.mm20.launcher2.ui.theme.WallpaperColors
|
|
||||||
import palettes.TonalPalette
|
|
||||||
import scheme.Scheme
|
|
||||||
|
|
||||||
fun MaterialYouCompatScheme(wallpaperColors: WallpaperColors, darkTheme: Boolean): ColorScheme {
|
|
||||||
val scheme = if (darkTheme) {
|
|
||||||
Scheme.dark(wallpaperColors.primary.toArgb())
|
|
||||||
} else {
|
|
||||||
Scheme.light(wallpaperColors.primary.toArgb())
|
|
||||||
}
|
|
||||||
return ColorScheme(
|
|
||||||
primary = Color(scheme.primary),
|
|
||||||
onPrimary = Color(scheme.onPrimary),
|
|
||||||
primaryContainer = Color(scheme.primaryContainer),
|
|
||||||
onPrimaryContainer = Color(scheme.onPrimaryContainer),
|
|
||||||
secondary = Color(scheme.secondary),
|
|
||||||
onSecondary = Color(scheme.onSecondary),
|
|
||||||
secondaryContainer = Color(scheme.secondaryContainer),
|
|
||||||
onSecondaryContainer = Color(scheme.onSecondaryContainer),
|
|
||||||
tertiary = Color(scheme.tertiary),
|
|
||||||
onTertiary = Color(scheme.onTertiary),
|
|
||||||
tertiaryContainer = Color(scheme.tertiaryContainer),
|
|
||||||
onTertiaryContainer = Color(scheme.onTertiaryContainer),
|
|
||||||
background = Color(scheme.background),
|
|
||||||
onBackground = Color(scheme.onBackground),
|
|
||||||
surface = Color(scheme.surface),
|
|
||||||
onSurface = Color(scheme.onSurface),
|
|
||||||
surfaceVariant = Color(scheme.surfaceVariant),
|
|
||||||
onSurfaceVariant = Color(scheme.onSurfaceVariant),
|
|
||||||
outline = Color(scheme.outline),
|
|
||||||
inverseSurface = Color(scheme.inverseSurface),
|
|
||||||
inverseOnSurface = Color(scheme.inverseOnSurface),
|
|
||||||
inversePrimary = Color(scheme.inversePrimary),
|
|
||||||
surfaceTint = Color(scheme.primary),
|
|
||||||
error = Color(scheme.error),
|
|
||||||
onError = Color(scheme.onError),
|
|
||||||
errorContainer = Color(scheme.errorContainer),
|
|
||||||
onErrorContainer = Color(scheme.onErrorContainer),
|
|
||||||
scrim = Color(scheme.scrim),
|
|
||||||
outlineVariant = Color(scheme.outlineVariant),
|
|
||||||
surfaceBright = Color(scheme.surfaceBright),
|
|
||||||
surfaceContainer = Color(scheme.surfaceContainer),
|
|
||||||
surfaceContainerHigh = Color(scheme.surfaceContainerHigh),
|
|
||||||
surfaceContainerHighest = Color(scheme.surfaceContainerHighest),
|
|
||||||
surfaceContainerLow = Color(scheme.surfaceContainerLow),
|
|
||||||
surfaceContainerLowest = Color(scheme.surfaceContainerLowest),
|
|
||||||
surfaceDim = Color(scheme.surfaceDim),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -0,0 +1,64 @@
|
|||||||
|
package de.mm20.launcher2.ui.theme.shapes
|
||||||
|
|
||||||
|
import androidx.compose.foundation.shape.CornerBasedShape
|
||||||
|
import androidx.compose.foundation.shape.CornerSize
|
||||||
|
import androidx.compose.foundation.shape.CutCornerShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.Shapes
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import de.mm20.launcher2.themes.CornerStyle
|
||||||
|
import de.mm20.launcher2.themes.Shape as ThemeShape
|
||||||
|
import de.mm20.launcher2.themes.Shapes as ThemeShapes
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun shapesOf(shapes: ThemeShapes): Shapes {
|
||||||
|
return remember(shapes) {
|
||||||
|
Shapes(
|
||||||
|
extraSmall = fromShape(shapes.extraSmall, shapes.baseShape, 1f / 3f),
|
||||||
|
small = fromShape(shapes.small, shapes.baseShape, 2f / 3f),
|
||||||
|
medium = fromShape(shapes.medium, shapes.baseShape, 1f),
|
||||||
|
large = fromShape(shapes.large, shapes.baseShape, 4f / 3f),
|
||||||
|
largeIncreased = fromShape(shapes.largeIncreased, shapes.baseShape, 5f / 3f),
|
||||||
|
extraLarge = fromShape(shapes.extraLarge, shapes.baseShape, 7f / 3f),
|
||||||
|
extraLargeIncreased = fromShape(shapes.extraLargeIncreased, shapes.baseShape, 8f / 3f),
|
||||||
|
extraExtraLarge = fromShape(shapes.extraExtraLarge, shapes.baseShape, 12f / 3f),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fromShape(shape: ThemeShape?, baseShape: ThemeShape, factor: Float): CornerBasedShape {
|
||||||
|
val topStart = getCornerRadius(shape, baseShape, factor, 0)
|
||||||
|
val topEnd = getCornerRadius(shape, baseShape, factor, 1)
|
||||||
|
val bottomEnd = getCornerRadius(shape, baseShape, factor, 2)
|
||||||
|
val bottomStart = getCornerRadius(shape, baseShape, factor, 3)
|
||||||
|
|
||||||
|
return if ((shape?.corners ?: baseShape.corners) == CornerStyle.Cut) {
|
||||||
|
CutCornerShape(
|
||||||
|
topStart = topStart,
|
||||||
|
topEnd = topEnd,
|
||||||
|
bottomEnd = bottomEnd,
|
||||||
|
bottomStart = bottomStart
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
RoundedCornerShape(
|
||||||
|
topStart = topStart,
|
||||||
|
topEnd = topEnd,
|
||||||
|
bottomEnd = bottomEnd,
|
||||||
|
bottomStart = bottomStart
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCornerRadius(
|
||||||
|
shape: ThemeShape?,
|
||||||
|
baseShape: ThemeShape,
|
||||||
|
factor: Float,
|
||||||
|
index: Int
|
||||||
|
): CornerSize {
|
||||||
|
return CornerSize(
|
||||||
|
(shape?.radii?.get(index)?.toFloat() ?: ((baseShape.radii?.get(index)?.toFloat()
|
||||||
|
?: 12f) * factor)).dp
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -1741,3 +1741,43 @@ private val _BreezyWeather = materialIcon("Icons.Rounded.BreezyWeather") {
|
|||||||
|
|
||||||
val Icons.Rounded.BreezyWeather
|
val Icons.Rounded.BreezyWeather
|
||||||
get() = _BreezyWeather
|
get() = _BreezyWeather
|
||||||
|
|
||||||
|
val _CutCorner = materialIcon("Icons.Rounded.CutCorner") {
|
||||||
|
materialPath {
|
||||||
|
moveTo(2f, 3f)
|
||||||
|
verticalLineTo(22f)
|
||||||
|
horizontalLineTo(21f)
|
||||||
|
verticalLineTo(11.585938f)
|
||||||
|
lineTo(12.414063f, 3f)
|
||||||
|
close()
|
||||||
|
moveToRelative(2f, 2f)
|
||||||
|
horizontalLineToRelative(7.585938f)
|
||||||
|
lineTo(19f, 12.414063f)
|
||||||
|
verticalLineTo(20f)
|
||||||
|
horizontalLineTo(4f)
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val Icons.Rounded.CutCorner
|
||||||
|
get() = _CutCorner
|
||||||
|
|
||||||
|
val _RoundedCornerAlt = materialIcon("Icons.Rounded.RoundedCornerAlt") {
|
||||||
|
materialPath {
|
||||||
|
moveTo(2f, 3f)
|
||||||
|
verticalLineTo(22f)
|
||||||
|
horizontalLineTo(21f)
|
||||||
|
verticalLineTo(12f)
|
||||||
|
curveTo(21f, 7.0412819f, 16.958718f, 3f, 12f, 3f)
|
||||||
|
close()
|
||||||
|
moveToRelative(2f, 2f)
|
||||||
|
horizontalLineToRelative(8f)
|
||||||
|
curveToRelative(3.877838f, 0f, 7f, 3.1221621f, 7f, 7f)
|
||||||
|
verticalLineToRelative(8f)
|
||||||
|
horizontalLineTo(4f)
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val Icons.Rounded.RoundedCornerAlt
|
||||||
|
get() = _RoundedCornerAlt
|
||||||
@ -438,6 +438,11 @@
|
|||||||
<string name="preference_mdy_color_source">Source for dynamic colors</string>
|
<string name="preference_mdy_color_source">Source for dynamic colors</string>
|
||||||
<string name="preference_mdy_color_source_system">System</string>
|
<string name="preference_mdy_color_source_system">System</string>
|
||||||
<string name="preference_mdy_color_source_wallpaper">Wallpaper</string>
|
<string name="preference_mdy_color_source_wallpaper">Wallpaper</string>
|
||||||
|
<string name="preference_screen_shapes">Shapes</string>
|
||||||
|
<string name="preference_shapes_default">Default</string>
|
||||||
|
<string name="preference_shapes_extra_round">Extra round</string>
|
||||||
|
<string name="preference_shapes_rect">Rectangualar</string>
|
||||||
|
<string name="preference_shapes_base">Base shape</string>
|
||||||
<string name="preference_font">Font</string>
|
<string name="preference_font">Font</string>
|
||||||
<string name="preference_font_system">System default</string>
|
<string name="preference_font_system">System default</string>
|
||||||
<string name="preference_screen_about">About</string>
|
<string name="preference_screen_about">About</string>
|
||||||
@ -819,7 +824,9 @@
|
|||||||
<string name="note_widget_file_write_error">Error saving note</string>
|
<string name="note_widget_file_write_error">Error saving note</string>
|
||||||
<string name="note_widget_file_write_error_description">The note could not be written to the linked file. Possibly, it has been moved or deleted. A copy has been saved to the launcher\'s internal storage.</string>
|
<string name="note_widget_file_write_error_description">The note could not be written to the linked file. Possibly, it has been moved or deleted. A copy has been saved to the launcher\'s internal storage.</string>
|
||||||
<string name="confirmation_delete_color_scheme">Do you really want to delete the color scheme %1$s\?</string>
|
<string name="confirmation_delete_color_scheme">Do you really want to delete the color scheme %1$s\?</string>
|
||||||
|
<string name="confirmation_delete_shapes_scheme">Do you really want to delete the shapes scheme %1$s\?</string>
|
||||||
<string name="new_color_scheme_name">New color scheme</string>
|
<string name="new_color_scheme_name">New color scheme</string>
|
||||||
|
<string name="new_shapes_name">New shapes</string>
|
||||||
<string name="theme_color_scheme_system_default">Use system default</string>
|
<string name="theme_color_scheme_system_default">Use system default</string>
|
||||||
<string name="theme_color_scheme_autogenerate">From primary color</string>
|
<string name="theme_color_scheme_autogenerate">From primary color</string>
|
||||||
<string name="theme_color_scheme_palette_color">Palette</string>
|
<string name="theme_color_scheme_palette_color">Palette</string>
|
||||||
|
|||||||
@ -11,7 +11,10 @@ data class LauncherSettingsData internal constructor(
|
|||||||
val schemaVersion: Int = 5,
|
val schemaVersion: Int = 5,
|
||||||
|
|
||||||
val uiColorScheme: ColorScheme = ColorScheme.System,
|
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 uiCompatModeColors: Boolean = false,
|
||||||
val uiFont: Font = Font.Outfit,
|
val uiFont: Font = Font.Outfit,
|
||||||
@Deprecated("No longer in use, only used for migration")
|
@Deprecated("No longer in use, only used for migration")
|
||||||
@ -121,8 +124,10 @@ data class LauncherSettingsData internal constructor(
|
|||||||
val systemBarsNavColors: SystemBarColors = SystemBarColors.Auto,
|
val systemBarsNavColors: SystemBarColors = SystemBarColors.Auto,
|
||||||
|
|
||||||
val surfacesOpacity: Float = 1f,
|
val surfacesOpacity: Float = 1f,
|
||||||
|
@Deprecated("Replaces with shape schemes")
|
||||||
val surfacesRadius: Int = 24,
|
val surfacesRadius: Int = 24,
|
||||||
val surfacesBorderWidth: Int = 0,
|
val surfacesBorderWidth: Int = 0,
|
||||||
|
@Deprecated("Replaces with shape schemes")
|
||||||
val surfacesShape: SurfaceShape = SurfaceShape.Rounded,
|
val surfacesShape: SurfaceShape = SurfaceShape.Rounded,
|
||||||
|
|
||||||
val widgetsEditButton: Boolean = true,
|
val widgetsEditButton: Boolean = true,
|
||||||
@ -201,20 +206,45 @@ enum class Font {
|
|||||||
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
sealed interface ThemeDescriptor {
|
sealed interface ColorsDescriptor {
|
||||||
@Serializable
|
@Serializable
|
||||||
@SerialName("default")
|
@SerialName("default")
|
||||||
data object Default : ThemeDescriptor
|
data object Default : ColorsDescriptor
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@SerialName("bw")
|
@SerialName("bw")
|
||||||
data object BlackAndWhite : ThemeDescriptor
|
data object BlackAndWhite : ColorsDescriptor
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@SerialName("custom")
|
@SerialName("custom")
|
||||||
data class Custom(
|
data class Custom(
|
||||||
val id: String,
|
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 {
|
internal enum class ClockWidgetStyleEnum {
|
||||||
|
|||||||
@ -7,16 +7,14 @@ import de.mm20.launcher2.preferences.LauncherDataStore
|
|||||||
import de.mm20.launcher2.preferences.ScreenOrientation
|
import de.mm20.launcher2.preferences.ScreenOrientation
|
||||||
import de.mm20.launcher2.preferences.SearchBarColors
|
import de.mm20.launcher2.preferences.SearchBarColors
|
||||||
import de.mm20.launcher2.preferences.SearchBarStyle
|
import de.mm20.launcher2.preferences.SearchBarStyle
|
||||||
import de.mm20.launcher2.preferences.SurfaceShape
|
|
||||||
import de.mm20.launcher2.preferences.SystemBarColors
|
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.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
data class CardStyle(
|
data class CardStyle(
|
||||||
val opacity: Float = 1f,
|
val opacity: Float = 1f,
|
||||||
val cornerRadius: Int = 0,
|
|
||||||
val shape: SurfaceShape = SurfaceShape.Rounded,
|
|
||||||
val borderWidth: Int = 0,
|
val borderWidth: Int = 0,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -90,8 +88,6 @@ class UiSettings internal constructor(
|
|||||||
get() = launcherDataStore.data.map {
|
get() = launcherDataStore.data.map {
|
||||||
CardStyle(
|
CardStyle(
|
||||||
opacity = it.surfacesOpacity,
|
opacity = it.surfacesOpacity,
|
||||||
cornerRadius = it.surfacesRadius,
|
|
||||||
shape = it.surfacesShape,
|
|
||||||
borderWidth = it.surfacesBorderWidth,
|
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) {
|
fun setCardBorderWidth(borderWidth: Int) {
|
||||||
launcherDataStore.update {
|
launcherDataStore.update {
|
||||||
it.copy(surfacesBorderWidth = borderWidth)
|
it.copy(surfacesBorderWidth = borderWidth)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setCardShape(shape: SurfaceShape) {
|
|
||||||
launcherDataStore.update {
|
|
||||||
it.copy(surfacesShape = shape)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val dimWallpaper
|
val dimWallpaper
|
||||||
get() = launcherDataStore.data.map {
|
get() = launcherDataStore.data.map {
|
||||||
it.wallpaperDim
|
it.wallpaperDim
|
||||||
@ -302,14 +286,25 @@ class UiSettings internal constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
val theme
|
val colors
|
||||||
get() = launcherDataStore.data.map {
|
get() = launcherDataStore.data.map {
|
||||||
it.uiTheme
|
it.uiColors
|
||||||
}.distinctUntilChanged()
|
}.distinctUntilChanged()
|
||||||
|
|
||||||
fun setTheme(theme: ThemeDescriptor) {
|
fun setColors(colors: ColorsDescriptor) {
|
||||||
launcherDataStore.update {
|
launcherDataStore.update {
|
||||||
it.copy(uiTheme = theme)
|
it.copy(uiColors = colors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val shapes
|
||||||
|
get() = launcherDataStore.data.map {
|
||||||
|
it.uiShapes
|
||||||
|
}.distinctUntilChanged()
|
||||||
|
|
||||||
|
fun setShapes(shapes: ShapesDescriptor) {
|
||||||
|
launcherDataStore.update {
|
||||||
|
it.copy(uiShapes = shapes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -18,7 +18,8 @@ import de.mm20.launcher2.database.entities.IconPackEntity
|
|||||||
import de.mm20.launcher2.database.entities.PluginEntity
|
import de.mm20.launcher2.database.entities.PluginEntity
|
||||||
import de.mm20.launcher2.database.entities.SavedSearchableEntity
|
import de.mm20.launcher2.database.entities.SavedSearchableEntity
|
||||||
import de.mm20.launcher2.database.entities.SearchActionEntity
|
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.entities.WidgetEntity
|
||||||
import de.mm20.launcher2.database.migrations.Migration_10_11
|
import de.mm20.launcher2.database.migrations.Migration_10_11
|
||||||
import de.mm20.launcher2.database.migrations.Migration_11_12
|
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_24_25
|
||||||
import de.mm20.launcher2.database.migrations.Migration_25_26
|
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_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_6_7
|
||||||
import de.mm20.launcher2.database.migrations.Migration_7_8
|
import de.mm20.launcher2.database.migrations.Migration_7_8
|
||||||
import de.mm20.launcher2.database.migrations.Migration_8_9
|
import de.mm20.launcher2.database.migrations.Migration_8_9
|
||||||
@ -54,9 +56,10 @@ import java.util.UUID
|
|||||||
WidgetEntity::class,
|
WidgetEntity::class,
|
||||||
CustomAttributeEntity::class,
|
CustomAttributeEntity::class,
|
||||||
SearchActionEntity::class,
|
SearchActionEntity::class,
|
||||||
ThemeEntity::class,
|
ColorsEntity::class,
|
||||||
PluginEntity::class,
|
PluginEntity::class,
|
||||||
], version = 27, exportSchema = true
|
ShapesEntity::class,
|
||||||
|
], version = 28, exportSchema = true
|
||||||
)
|
)
|
||||||
@TypeConverters(ComponentNameConverter::class)
|
@TypeConverters(ComponentNameConverter::class)
|
||||||
abstract class AppDatabase : RoomDatabase() {
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
@ -156,6 +159,7 @@ abstract class AppDatabase : RoomDatabase() {
|
|||||||
Migration_24_25(),
|
Migration_24_25(),
|
||||||
Migration_25_26(),
|
Migration_25_26(),
|
||||||
Migration_26_27(),
|
Migration_26_27(),
|
||||||
|
Migration_27_28(),
|
||||||
).build()
|
).build()
|
||||||
if (_instance == null) _instance = instance
|
if (_instance == null) _instance = instance
|
||||||
return instance
|
return instance
|
||||||
|
|||||||
@ -4,30 +4,52 @@ import androidx.room.Dao
|
|||||||
import androidx.room.Insert
|
import androidx.room.Insert
|
||||||
import androidx.room.Query
|
import androidx.room.Query
|
||||||
import androidx.room.Update
|
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 kotlinx.coroutines.flow.Flow
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
interface ThemeDao {
|
interface ThemeDao {
|
||||||
@Query("SELECT * FROM Theme")
|
@Query("SELECT * FROM Theme")
|
||||||
fun getAll(): Flow<List<ThemeEntity>>
|
fun getAllColors(): Flow<List<ColorsEntity>>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM Shapes")
|
||||||
|
fun getAllShapes(): Flow<List<ShapesEntity>>
|
||||||
|
|
||||||
@Query("SELECT * FROM Theme WHERE id = :id LIMIT 1")
|
@Query("SELECT * FROM Theme WHERE id = :id LIMIT 1")
|
||||||
fun get(id: UUID): Flow<ThemeEntity?>
|
fun getColors(id: UUID): Flow<ColorsEntity?>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM Shapes WHERE id = :id LIMIT 1")
|
||||||
|
fun getShapes(id: UUID): Flow<ShapesEntity?>
|
||||||
|
|
||||||
@Insert
|
@Insert
|
||||||
suspend fun insert(theme: ThemeEntity)
|
suspend fun insertColors(colors: ColorsEntity)
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
suspend fun insertShapes(shapes: ShapesEntity)
|
||||||
|
|
||||||
@Update
|
@Update
|
||||||
suspend fun update(theme: ThemeEntity)
|
suspend fun updateColors(colors: ColorsEntity)
|
||||||
|
|
||||||
|
@Update
|
||||||
|
suspend fun updateShapes(shapes: ShapesEntity)
|
||||||
|
|
||||||
@Query("DELETE FROM Theme WHERE id = :id")
|
@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")
|
@Query("DELETE FROM Theme")
|
||||||
suspend fun deleteAll()
|
suspend fun deleteAllColors()
|
||||||
|
|
||||||
|
@Query("DELETE FROM Shapes")
|
||||||
|
suspend fun deleteAllShapes()
|
||||||
|
|
||||||
@Insert
|
@Insert
|
||||||
fun insertAll(themes: List<ThemeEntity>)
|
fun insertAllColors(colors: List<ColorsEntity>)
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
fun insertAllShapes(shapes: List<ShapesEntity>)
|
||||||
}
|
}
|
||||||
@ -5,7 +5,7 @@ import androidx.room.PrimaryKey
|
|||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
@Entity(tableName = "Theme")
|
@Entity(tableName = "Theme")
|
||||||
data class ThemeEntity(
|
data class ColorsEntity(
|
||||||
@PrimaryKey val id: UUID,
|
@PrimaryKey val id: UUID,
|
||||||
val name: String,
|
val name: String,
|
||||||
|
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
package de.mm20.launcher2.database.entities
|
||||||
|
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
@Entity(tableName = "Shapes")
|
||||||
|
data class ShapesEntity(
|
||||||
|
@PrimaryKey val id: UUID,
|
||||||
|
val name: String,
|
||||||
|
|
||||||
|
val baseShape: String,
|
||||||
|
|
||||||
|
val extraSmall: String? = null,
|
||||||
|
val small: String? = null,
|
||||||
|
val medium: String? = null,
|
||||||
|
val large: String? = null,
|
||||||
|
val largeIncreased: String? = null,
|
||||||
|
val extraLarge: String? = null,
|
||||||
|
val extraLargeIncreased: String? = null,
|
||||||
|
val extraExtraLarge: String? = null,
|
||||||
|
)
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
package de.mm20.launcher2.database.migrations
|
||||||
|
|
||||||
|
import androidx.room.migration.Migration
|
||||||
|
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||||
|
|
||||||
|
class Migration_27_28: Migration(27, 28) {
|
||||||
|
|
||||||
|
override fun migrate(db: SupportSQLiteDatabase) {
|
||||||
|
db.execSQL(
|
||||||
|
"""
|
||||||
|
CREATE TABLE IF NOT EXISTS `Shapes` (
|
||||||
|
`id` BLOB NOT NULL PRIMARY KEY,
|
||||||
|
`name` TEXT NOT NULL,
|
||||||
|
`baseShape` TEXT NOT NULL,
|
||||||
|
`extraSmall` TEXT,
|
||||||
|
`small` TEXT,
|
||||||
|
`medium` TEXT,
|
||||||
|
`large` TEXT,
|
||||||
|
`largeIncreased` TEXT,
|
||||||
|
`extraLarge` TEXT,
|
||||||
|
`extraLargeIncreased` TEXT,
|
||||||
|
`extraExtraLarge` TEXT
|
||||||
|
)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,138 +1,13 @@
|
|||||||
package de.mm20.launcher2.themes
|
package de.mm20.launcher2.themes
|
||||||
|
|
||||||
import de.mm20.launcher2.database.entities.ThemeEntity
|
import de.mm20.launcher2.database.entities.ColorsEntity
|
||||||
import hct.Hct
|
import hct.Hct
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.Transient
|
import kotlinx.serialization.Transient
|
||||||
import java.util.UUID
|
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
|
@Serializable
|
||||||
data class CorePalette<out T : Int?>(
|
data class Colors(
|
||||||
val primary: T,
|
|
||||||
val secondary: T,
|
|
||||||
val tertiary: T,
|
|
||||||
val neutral: T,
|
|
||||||
val neutralVariant: T,
|
|
||||||
val error: T,
|
|
||||||
)
|
|
||||||
|
|
||||||
val EmptyCorePalette = CorePalette<Int?>(null, null, null, null, null, null)
|
|
||||||
|
|
||||||
typealias FullCorePalette = CorePalette<Int>
|
|
||||||
typealias PartialCorePalette = CorePalette<Int?>
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class ColorScheme<out T : Color?>(
|
|
||||||
val primary: T,
|
|
||||||
val onPrimary: T,
|
|
||||||
val primaryContainer: T,
|
|
||||||
val onPrimaryContainer: T,
|
|
||||||
val secondary: T,
|
|
||||||
val onSecondary: T,
|
|
||||||
val secondaryContainer: T,
|
|
||||||
val onSecondaryContainer: T,
|
|
||||||
val tertiary: T,
|
|
||||||
val onTertiary: T,
|
|
||||||
val tertiaryContainer: T,
|
|
||||||
val onTertiaryContainer: T,
|
|
||||||
val error: T,
|
|
||||||
val onError: T,
|
|
||||||
val errorContainer: T,
|
|
||||||
val onErrorContainer: T,
|
|
||||||
val surface: T,
|
|
||||||
val onSurface: T,
|
|
||||||
val onSurfaceVariant: T,
|
|
||||||
val outline: T,
|
|
||||||
val outlineVariant: T,
|
|
||||||
val inverseSurface: T,
|
|
||||||
val inverseOnSurface: T,
|
|
||||||
val inversePrimary: T,
|
|
||||||
val surfaceDim: T,
|
|
||||||
val surfaceBright: T,
|
|
||||||
val surfaceContainerLowest: T,
|
|
||||||
val surfaceContainerLow: T,
|
|
||||||
val surfaceContainer: T,
|
|
||||||
val surfaceContainerHigh: T,
|
|
||||||
val surfaceContainerHighest: T,
|
|
||||||
|
|
||||||
val background: T,
|
|
||||||
val onBackground: T,
|
|
||||||
val surfaceTint: T,
|
|
||||||
val scrim: T,
|
|
||||||
val surfaceVariant: T,
|
|
||||||
)
|
|
||||||
|
|
||||||
typealias FullColorScheme = ColorScheme<Color>
|
|
||||||
typealias PartialColorScheme = ColorScheme<Color?>
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class Theme(
|
|
||||||
@Transient val id: UUID = UUID.randomUUID(),
|
@Transient val id: UUID = UUID.randomUUID(),
|
||||||
val builtIn: Boolean = false,
|
val builtIn: Boolean = false,
|
||||||
val name: String,
|
val name: String,
|
||||||
@ -141,7 +16,7 @@ data class Theme(
|
|||||||
val darkColorScheme: PartialColorScheme = DefaultDarkColorScheme,
|
val darkColorScheme: PartialColorScheme = DefaultDarkColorScheme,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
constructor(entity: ThemeEntity) : this(
|
constructor(entity: ColorsEntity) : this(
|
||||||
id = entity.id,
|
id = entity.id,
|
||||||
builtIn = false,
|
builtIn = false,
|
||||||
name = entity.name,
|
name = entity.name,
|
||||||
@ -154,86 +29,86 @@ data class Theme(
|
|||||||
error = entity.corePaletteE,
|
error = entity.corePaletteE,
|
||||||
),
|
),
|
||||||
lightColorScheme = ColorScheme(
|
lightColorScheme = ColorScheme(
|
||||||
primary = Color(entity.lightPrimary),
|
primary = Color.fromString(entity.lightPrimary),
|
||||||
onPrimary = Color(entity.lightOnPrimary),
|
onPrimary = Color.fromString(entity.lightOnPrimary),
|
||||||
primaryContainer = Color(entity.lightPrimaryContainer),
|
primaryContainer = Color.fromString(entity.lightPrimaryContainer),
|
||||||
onPrimaryContainer = Color(entity.lightOnPrimaryContainer),
|
onPrimaryContainer = Color.fromString(entity.lightOnPrimaryContainer),
|
||||||
secondary = Color(entity.lightSecondary),
|
secondary = Color.fromString(entity.lightSecondary),
|
||||||
onSecondary = Color(entity.lightOnSecondary),
|
onSecondary = Color.fromString(entity.lightOnSecondary),
|
||||||
secondaryContainer = Color(entity.lightSecondaryContainer),
|
secondaryContainer = Color.fromString(entity.lightSecondaryContainer),
|
||||||
onSecondaryContainer = Color(entity.lightOnSecondaryContainer),
|
onSecondaryContainer = Color.fromString(entity.lightOnSecondaryContainer),
|
||||||
tertiary = Color(entity.lightTertiary),
|
tertiary = Color.fromString(entity.lightTertiary),
|
||||||
onTertiary = Color(entity.lightOnTertiary),
|
onTertiary = Color.fromString(entity.lightOnTertiary),
|
||||||
tertiaryContainer = Color(entity.lightTertiaryContainer),
|
tertiaryContainer = Color.fromString(entity.lightTertiaryContainer),
|
||||||
onTertiaryContainer = Color(entity.lightOnTertiaryContainer),
|
onTertiaryContainer = Color.fromString(entity.lightOnTertiaryContainer),
|
||||||
error = Color(entity.lightError),
|
error = Color.fromString(entity.lightError),
|
||||||
onError = Color(entity.lightOnError),
|
onError = Color.fromString(entity.lightOnError),
|
||||||
errorContainer = Color(entity.lightErrorContainer),
|
errorContainer = Color.fromString(entity.lightErrorContainer),
|
||||||
onErrorContainer = Color(entity.lightOnErrorContainer),
|
onErrorContainer = Color.fromString(entity.lightOnErrorContainer),
|
||||||
surface = Color(entity.lightSurface),
|
surface = Color.fromString(entity.lightSurface),
|
||||||
onSurface = Color(entity.lightOnSurface),
|
onSurface = Color.fromString(entity.lightOnSurface),
|
||||||
onSurfaceVariant = Color(entity.lightOnSurfaceVariant),
|
onSurfaceVariant = Color.fromString(entity.lightOnSurfaceVariant),
|
||||||
outline = Color(entity.lightOutline),
|
outline = Color.fromString(entity.lightOutline),
|
||||||
outlineVariant = Color(entity.lightOutlineVariant),
|
outlineVariant = Color.fromString(entity.lightOutlineVariant),
|
||||||
inverseSurface = Color(entity.lightInverseSurface),
|
inverseSurface = Color.fromString(entity.lightInverseSurface),
|
||||||
inverseOnSurface = Color(entity.lightInverseOnSurface),
|
inverseOnSurface = Color.fromString(entity.lightInverseOnSurface),
|
||||||
inversePrimary = Color(entity.lightInversePrimary),
|
inversePrimary = Color.fromString(entity.lightInversePrimary),
|
||||||
surfaceDim = Color(entity.lightSurfaceDim),
|
surfaceDim = Color.fromString(entity.lightSurfaceDim),
|
||||||
surfaceBright = Color(entity.lightSurfaceBright),
|
surfaceBright = Color.fromString(entity.lightSurfaceBright),
|
||||||
surfaceContainerLowest = Color(entity.lightSurfaceContainerLowest),
|
surfaceContainerLowest = Color.fromString(entity.lightSurfaceContainerLowest),
|
||||||
surfaceContainerLow = Color(entity.lightSurfaceContainerLow),
|
surfaceContainerLow = Color.fromString(entity.lightSurfaceContainerLow),
|
||||||
surfaceContainer = Color(entity.lightSurfaceContainer),
|
surfaceContainer = Color.fromString(entity.lightSurfaceContainer),
|
||||||
surfaceContainerHigh = Color(entity.lightSurfaceContainerHigh),
|
surfaceContainerHigh = Color.fromString(entity.lightSurfaceContainerHigh),
|
||||||
surfaceContainerHighest = Color(entity.lightSurfaceContainerHighest),
|
surfaceContainerHighest = Color.fromString(entity.lightSurfaceContainerHighest),
|
||||||
background = Color(entity.lightBackground),
|
background = Color.fromString(entity.lightBackground),
|
||||||
onBackground = Color(entity.lightOnBackground),
|
onBackground = Color.fromString(entity.lightOnBackground),
|
||||||
surfaceTint = Color(entity.lightSurfaceTint),
|
surfaceTint = Color.fromString(entity.lightSurfaceTint),
|
||||||
scrim = Color(entity.lightScrim),
|
scrim = Color.fromString(entity.lightScrim),
|
||||||
surfaceVariant = Color(entity.lightSurfaceVariant),
|
surfaceVariant = Color.fromString(entity.lightSurfaceVariant),
|
||||||
),
|
),
|
||||||
darkColorScheme = ColorScheme(
|
darkColorScheme = ColorScheme(
|
||||||
primary = Color(entity.darkPrimary),
|
primary = Color.fromString(entity.darkPrimary),
|
||||||
onPrimary = Color(entity.darkOnPrimary),
|
onPrimary = Color.fromString(entity.darkOnPrimary),
|
||||||
primaryContainer = Color(entity.darkPrimaryContainer),
|
primaryContainer = Color.fromString(entity.darkPrimaryContainer),
|
||||||
onPrimaryContainer = Color(entity.darkOnPrimaryContainer),
|
onPrimaryContainer = Color.fromString(entity.darkOnPrimaryContainer),
|
||||||
secondary = Color(entity.darkSecondary),
|
secondary = Color.fromString(entity.darkSecondary),
|
||||||
onSecondary = Color(entity.darkOnSecondary),
|
onSecondary = Color.fromString(entity.darkOnSecondary),
|
||||||
secondaryContainer = Color(entity.darkSecondaryContainer),
|
secondaryContainer = Color.fromString(entity.darkSecondaryContainer),
|
||||||
onSecondaryContainer = Color(entity.darkOnSecondaryContainer),
|
onSecondaryContainer = Color.fromString(entity.darkOnSecondaryContainer),
|
||||||
tertiary = Color(entity.darkTertiary),
|
tertiary = Color.fromString(entity.darkTertiary),
|
||||||
onTertiary = Color(entity.darkOnTertiary),
|
onTertiary = Color.fromString(entity.darkOnTertiary),
|
||||||
tertiaryContainer = Color(entity.darkTertiaryContainer),
|
tertiaryContainer = Color.fromString(entity.darkTertiaryContainer),
|
||||||
onTertiaryContainer = Color(entity.darkOnTertiaryContainer),
|
onTertiaryContainer = Color.fromString(entity.darkOnTertiaryContainer),
|
||||||
error = Color(entity.darkError),
|
error = Color.fromString(entity.darkError),
|
||||||
onError = Color(entity.darkOnError),
|
onError = Color.fromString(entity.darkOnError),
|
||||||
errorContainer = Color(entity.darkErrorContainer),
|
errorContainer = Color.fromString(entity.darkErrorContainer),
|
||||||
onErrorContainer = Color(entity.darkOnErrorContainer),
|
onErrorContainer = Color.fromString(entity.darkOnErrorContainer),
|
||||||
surface = Color(entity.darkSurface),
|
surface = Color.fromString(entity.darkSurface),
|
||||||
onSurface = Color(entity.darkOnSurface),
|
onSurface = Color.fromString(entity.darkOnSurface),
|
||||||
onSurfaceVariant = Color(entity.darkOnSurfaceVariant),
|
onSurfaceVariant = Color.fromString(entity.darkOnSurfaceVariant),
|
||||||
outline = Color(entity.darkOutline),
|
outline = Color.fromString(entity.darkOutline),
|
||||||
outlineVariant = Color(entity.darkOutlineVariant),
|
outlineVariant = Color.fromString(entity.darkOutlineVariant),
|
||||||
inverseSurface = Color(entity.darkInverseSurface),
|
inverseSurface = Color.fromString(entity.darkInverseSurface),
|
||||||
inverseOnSurface = Color(entity.darkInverseOnSurface),
|
inverseOnSurface = Color.fromString(entity.darkInverseOnSurface),
|
||||||
inversePrimary = Color(entity.darkInversePrimary),
|
inversePrimary = Color.fromString(entity.darkInversePrimary),
|
||||||
surfaceDim = Color(entity.darkSurfaceDim),
|
surfaceDim = Color.fromString(entity.darkSurfaceDim),
|
||||||
surfaceBright = Color(entity.darkSurfaceBright),
|
surfaceBright = Color.fromString(entity.darkSurfaceBright),
|
||||||
surfaceContainerLowest = Color(entity.darkSurfaceContainerLowest),
|
surfaceContainerLowest = Color.fromString(entity.darkSurfaceContainerLowest),
|
||||||
surfaceContainerLow = Color(entity.darkSurfaceContainerLow),
|
surfaceContainerLow = Color.fromString(entity.darkSurfaceContainerLow),
|
||||||
surfaceContainer = Color(entity.darkSurfaceContainer),
|
surfaceContainer = Color.fromString(entity.darkSurfaceContainer),
|
||||||
surfaceContainerHigh = Color(entity.darkSurfaceContainerHigh),
|
surfaceContainerHigh = Color.fromString(entity.darkSurfaceContainerHigh),
|
||||||
surfaceContainerHighest = Color(entity.darkSurfaceContainerHighest),
|
surfaceContainerHighest = Color.fromString(entity.darkSurfaceContainerHighest),
|
||||||
background = Color(entity.darkBackground),
|
background = Color.fromString(entity.darkBackground),
|
||||||
onBackground = Color(entity.darkOnBackground),
|
onBackground = Color.fromString(entity.darkOnBackground),
|
||||||
surfaceTint = Color(entity.darkSurfaceTint),
|
surfaceTint = Color.fromString(entity.darkSurfaceTint),
|
||||||
scrim = Color(entity.darkScrim),
|
scrim = Color.fromString(entity.darkScrim),
|
||||||
surfaceVariant = Color(entity.darkSurfaceVariant),
|
surfaceVariant = Color.fromString(entity.darkSurfaceVariant),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
internal fun toEntity(): ThemeEntity {
|
internal fun toEntity(): ColorsEntity {
|
||||||
return ThemeEntity(
|
return ColorsEntity(
|
||||||
id = id,
|
id = id,
|
||||||
name = name,
|
name = name,
|
||||||
corePaletteA1 = corePalette.primary,
|
corePaletteA1 = corePalette.primary,
|
||||||
@ -320,6 +195,137 @@ data class Theme(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class CorePaletteColor {
|
||||||
|
Primary,
|
||||||
|
Secondary,
|
||||||
|
Tertiary,
|
||||||
|
Neutral,
|
||||||
|
NeutralVariant,
|
||||||
|
Error;
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return when (this) {
|
||||||
|
Primary -> "p"
|
||||||
|
Secondary -> "s"
|
||||||
|
Tertiary -> "t"
|
||||||
|
Neutral -> "n"
|
||||||
|
NeutralVariant -> "nv"
|
||||||
|
Error -> "e"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromString(string: String): CorePaletteColor? {
|
||||||
|
return when (string) {
|
||||||
|
"p" -> Primary
|
||||||
|
"s" -> Secondary
|
||||||
|
"t" -> Tertiary
|
||||||
|
"n" -> Neutral
|
||||||
|
"nv" -> NeutralVariant
|
||||||
|
"e" -> Error
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable(with = ColorSerializer::class)
|
||||||
|
sealed interface Color {
|
||||||
|
companion object {
|
||||||
|
fun fromString(string: String?): Color? {
|
||||||
|
|
||||||
|
if (string == null) return null
|
||||||
|
if (string.startsWith("#")) {
|
||||||
|
return StaticColor(string.substring(1).toLongOrNull(16)?.toInt() ?: return null)
|
||||||
|
}
|
||||||
|
if (string.startsWith("$")) {
|
||||||
|
val parts = string.substring(1).split(".").takeIf { it.size == 2 } ?: return null
|
||||||
|
val color = CorePaletteColor.fromString(parts[0]) ?: return null
|
||||||
|
return ColorRef(
|
||||||
|
color = color,
|
||||||
|
tone = parts[1].toIntOrNull() ?: return null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ColorRef(
|
||||||
|
val color: CorePaletteColor,
|
||||||
|
val tone: Int,
|
||||||
|
) : Color {
|
||||||
|
override fun toString(): String {
|
||||||
|
return "$$color.$tone"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmInline
|
||||||
|
value class StaticColor(val color: Int) : Color {
|
||||||
|
override fun toString(): String {
|
||||||
|
return "#${color.toUInt().toString(16).padStart(8, '0')}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class CorePalette<out T : Int?>(
|
||||||
|
val primary: T,
|
||||||
|
val secondary: T,
|
||||||
|
val tertiary: T,
|
||||||
|
val neutral: T,
|
||||||
|
val neutralVariant: T,
|
||||||
|
val error: T,
|
||||||
|
)
|
||||||
|
|
||||||
|
val EmptyCorePalette = CorePalette<Int?>(null, null, null, null, null, null)
|
||||||
|
|
||||||
|
typealias FullCorePalette = CorePalette<Int>
|
||||||
|
typealias PartialCorePalette = CorePalette<Int?>
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ColorScheme<out T : Color?>(
|
||||||
|
val primary: T,
|
||||||
|
val onPrimary: T,
|
||||||
|
val primaryContainer: T,
|
||||||
|
val onPrimaryContainer: T,
|
||||||
|
val secondary: T,
|
||||||
|
val onSecondary: T,
|
||||||
|
val secondaryContainer: T,
|
||||||
|
val onSecondaryContainer: T,
|
||||||
|
val tertiary: T,
|
||||||
|
val onTertiary: T,
|
||||||
|
val tertiaryContainer: T,
|
||||||
|
val onTertiaryContainer: T,
|
||||||
|
val error: T,
|
||||||
|
val onError: T,
|
||||||
|
val errorContainer: T,
|
||||||
|
val onErrorContainer: T,
|
||||||
|
val surface: T,
|
||||||
|
val onSurface: T,
|
||||||
|
val onSurfaceVariant: T,
|
||||||
|
val outline: T,
|
||||||
|
val outlineVariant: T,
|
||||||
|
val inverseSurface: T,
|
||||||
|
val inverseOnSurface: T,
|
||||||
|
val inversePrimary: T,
|
||||||
|
val surfaceDim: T,
|
||||||
|
val surfaceBright: T,
|
||||||
|
val surfaceContainerLowest: T,
|
||||||
|
val surfaceContainerLow: T,
|
||||||
|
val surfaceContainer: T,
|
||||||
|
val surfaceContainerHigh: T,
|
||||||
|
val surfaceContainerHighest: T,
|
||||||
|
|
||||||
|
val background: T,
|
||||||
|
val onBackground: T,
|
||||||
|
val surfaceTint: T,
|
||||||
|
val scrim: T,
|
||||||
|
val surfaceVariant: T,
|
||||||
|
)
|
||||||
|
|
||||||
|
typealias FullColorScheme = ColorScheme<Color>
|
||||||
|
typealias PartialColorScheme = ColorScheme<Color?>
|
||||||
|
|
||||||
fun <T : Int?> CorePalette<T>.get(color: CorePaletteColor): T {
|
fun <T : Int?> CorePalette<T>.get(color: CorePaletteColor): T {
|
||||||
return when (color) {
|
return when (color) {
|
||||||
CorePaletteColor.Primary -> primary
|
CorePaletteColor.Primary -> primary
|
||||||
@ -4,6 +4,10 @@ import java.util.UUID
|
|||||||
|
|
||||||
|
|
||||||
val DefaultThemeId = UUID(0L, 0L)
|
val DefaultThemeId = UUID(0L, 0L)
|
||||||
|
val BlackAndWhiteThemeId = UUID(0L, 1L)
|
||||||
|
val ExtraRoundShapesId = UUID(0L, 1L)
|
||||||
|
val CutShapesId = UUID(0L, 2L)
|
||||||
|
val RectShapesId = UUID(0L, 3L)
|
||||||
|
|
||||||
val DefaultLightColorScheme = ColorScheme<Color>(
|
val DefaultLightColorScheme = ColorScheme<Color>(
|
||||||
primary = ColorRef(CorePaletteColor.Primary, 40),
|
primary = ColorRef(CorePaletteColor.Primary, 40),
|
||||||
@ -83,7 +87,6 @@ val DefaultDarkColorScheme = ColorScheme<Color>(
|
|||||||
scrim = ColorRef(CorePaletteColor.Neutral, 0),
|
scrim = ColorRef(CorePaletteColor.Neutral, 0),
|
||||||
)
|
)
|
||||||
|
|
||||||
val BlackAndWhiteThemeId = UUID(0L, 1L)
|
|
||||||
|
|
||||||
val BlackAndWhiteLightColorScheme = ColorScheme<Color?>(
|
val BlackAndWhiteLightColorScheme = ColorScheme<Color?>(
|
||||||
primary = StaticColor(0xFF000000.toInt()),
|
primary = StaticColor(0xFF000000.toInt()),
|
||||||
|
|||||||
@ -0,0 +1,67 @@
|
|||||||
|
package de.mm20.launcher2.themes
|
||||||
|
|
||||||
|
import kotlinx.serialization.KSerializer
|
||||||
|
import kotlinx.serialization.SerializationException
|
||||||
|
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||||
|
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||||
|
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||||
|
import kotlinx.serialization.encoding.Decoder
|
||||||
|
import kotlinx.serialization.encoding.Encoder
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.modules.SerializersModule
|
||||||
|
import kotlinx.serialization.modules.polymorphic
|
||||||
|
|
||||||
|
@Deprecated("Only used for backwards compatibility with old themes. New themes should use the new serialization format.")
|
||||||
|
internal class LegacyColorRefSerializer: KSerializer<ColorRef> {
|
||||||
|
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("$", PrimitiveKind.STRING)
|
||||||
|
|
||||||
|
override fun deserialize(decoder: Decoder): ColorRef {
|
||||||
|
return Color.fromString(decoder.decodeString()) as ColorRef
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun serialize(encoder: Encoder, value: ColorRef) {
|
||||||
|
encoder.encodeString(value.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("Only used for backwards compatibility with old themes. New themes should use the new serialization format.")
|
||||||
|
internal class LegacyStaticColorSerializer: KSerializer<StaticColor> {
|
||||||
|
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("#", PrimitiveKind.STRING)
|
||||||
|
override fun deserialize(decoder: Decoder): StaticColor {
|
||||||
|
return Color.fromString(decoder.decodeString()) as StaticColor
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun serialize(encoder: Encoder, value: StaticColor) {
|
||||||
|
encoder.encodeString(value.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("Only used for backwards compatibility with old themes. New themes should use the new serialization format.")
|
||||||
|
internal val legacyModule = SerializersModule {
|
||||||
|
polymorphic(Color::class) {
|
||||||
|
subclass(ColorRef::class, LegacyColorRefSerializer())
|
||||||
|
subclass(StaticColor::class, LegacyStaticColorSerializer())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("Only used for backwards compatibility with old themes. New themes should use the new serialization format.")
|
||||||
|
val LegacyThemeJson = Json {
|
||||||
|
serializersModule = legacyModule
|
||||||
|
useArrayPolymorphism = true
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("Only used for backwards compatibility with old themes. New themes should use the new serialization format.")
|
||||||
|
fun Colors.toLegacyJson(): String {
|
||||||
|
return LegacyThemeJson.encodeToString(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Deprecated("Only used for backwards compatibility with old themes. New themes should use the new serialization format.")
|
||||||
|
fun Colors.Companion.fromLegacyJson(json: String): Colors {
|
||||||
|
return try {
|
||||||
|
LegacyThemeJson.decodeFromString(json)
|
||||||
|
} catch (e: SerializationException) {
|
||||||
|
throw IllegalArgumentException(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,62 +1,39 @@
|
|||||||
package de.mm20.launcher2.themes
|
package de.mm20.launcher2.themes
|
||||||
|
|
||||||
import kotlinx.serialization.KSerializer
|
import kotlinx.serialization.KSerializer
|
||||||
import kotlinx.serialization.SerializationException
|
|
||||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||||
import kotlinx.serialization.encodeToString
|
|
||||||
import kotlinx.serialization.encoding.Decoder
|
import kotlinx.serialization.encoding.Decoder
|
||||||
import kotlinx.serialization.encoding.Encoder
|
import kotlinx.serialization.encoding.Encoder
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import kotlinx.serialization.modules.SerializersModule
|
|
||||||
import kotlinx.serialization.modules.contextual
|
|
||||||
import kotlinx.serialization.modules.polymorphic
|
|
||||||
|
|
||||||
internal class ColorRefSerializer: KSerializer<ColorRef> {
|
internal class ColorSerializer: KSerializer<Color> {
|
||||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("$", PrimitiveKind.STRING)
|
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ColorSerializer", PrimitiveKind.STRING)
|
||||||
|
|
||||||
override fun deserialize(decoder: Decoder): ColorRef {
|
override fun serialize(
|
||||||
return Color(decoder.decodeString()) as ColorRef
|
encoder: Encoder,
|
||||||
}
|
value: Color
|
||||||
|
) {
|
||||||
override fun serialize(encoder: Encoder, value: ColorRef) {
|
|
||||||
encoder.encodeString(value.toString())
|
encoder.encodeString(value.toString())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
internal class StaticColorSerializer: KSerializer<StaticColor> {
|
override fun deserialize(decoder: Decoder): Color {
|
||||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("#", PrimitiveKind.STRING)
|
TODO("Not yet implemented")
|
||||||
override fun deserialize(decoder: Decoder): StaticColor {
|
|
||||||
return Color(decoder.decodeString()) as StaticColor
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun serialize(encoder: Encoder, value: StaticColor) {
|
}
|
||||||
|
|
||||||
|
internal class ShapeSerializer: KSerializer<Shape> {
|
||||||
|
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ShapeSerializer", PrimitiveKind.STRING)
|
||||||
|
|
||||||
|
override fun serialize(
|
||||||
|
encoder: Encoder,
|
||||||
|
value: Shape
|
||||||
|
) {
|
||||||
encoder.encodeString(value.toString())
|
encoder.encodeString(value.toString())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
internal val module = SerializersModule {
|
override fun deserialize(decoder: Decoder): Shape {
|
||||||
polymorphic(Color::class) {
|
return Shape.fromString(decoder.decodeString())!!
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
120
data/themes/src/main/java/de/mm20/launcher2/themes/Shapes.kt
Normal file
120
data/themes/src/main/java/de/mm20/launcher2/themes/Shapes.kt
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
package de.mm20.launcher2.themes
|
||||||
|
|
||||||
|
import de.mm20.launcher2.database.entities.ShapesEntity
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.Transient
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Shapes(
|
||||||
|
@Transient val id: UUID = UUID.randomUUID(),
|
||||||
|
val builtIn: Boolean = false,
|
||||||
|
val name: String,
|
||||||
|
val baseShape: Shape = Shape(
|
||||||
|
corners = CornerStyle.Rounded,
|
||||||
|
radii = intArrayOf(12, 12, 12, 12),
|
||||||
|
),
|
||||||
|
val extraSmall: Shape? = null,
|
||||||
|
val small: Shape? = null,
|
||||||
|
val medium: Shape? = null,
|
||||||
|
val large: Shape? = null,
|
||||||
|
val largeIncreased: Shape? = null,
|
||||||
|
val extraLarge: Shape? = null,
|
||||||
|
val extraLargeIncreased: Shape? = null,
|
||||||
|
val extraExtraLarge: Shape? = null,
|
||||||
|
) {
|
||||||
|
constructor(entity: ShapesEntity) : this(
|
||||||
|
id = entity.id,
|
||||||
|
builtIn = false,
|
||||||
|
name = entity.name,
|
||||||
|
baseShape = Shape.fromString(entity.baseShape) ?: Shape(
|
||||||
|
corners = CornerStyle.Rounded,
|
||||||
|
radii = intArrayOf(12, 12, 12, 12)
|
||||||
|
),
|
||||||
|
extraSmall = Shape.fromString(entity.extraSmall),
|
||||||
|
small = Shape.fromString(entity.small),
|
||||||
|
medium = Shape.fromString(entity.medium),
|
||||||
|
large = Shape.fromString(entity.large),
|
||||||
|
largeIncreased = Shape.fromString(entity.largeIncreased),
|
||||||
|
extraLarge = Shape.fromString(entity.extraLarge),
|
||||||
|
extraLargeIncreased = Shape.fromString(entity.extraLargeIncreased),
|
||||||
|
extraExtraLarge = Shape.fromString(entity.extraExtraLarge),
|
||||||
|
)
|
||||||
|
|
||||||
|
internal fun toEntity(): ShapesEntity {
|
||||||
|
return ShapesEntity(
|
||||||
|
id = id,
|
||||||
|
name = name,
|
||||||
|
baseShape = baseShape.toString(),
|
||||||
|
extraSmall = extraSmall?.toString(),
|
||||||
|
small = small?.toString(),
|
||||||
|
medium = medium?.toString(),
|
||||||
|
large = large?.toString(),
|
||||||
|
largeIncreased = largeIncreased?.toString(),
|
||||||
|
extraLarge = extraLarge?.toString(),
|
||||||
|
extraLargeIncreased = extraLargeIncreased?.toString(),
|
||||||
|
extraExtraLarge = extraExtraLarge?.toString(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable(with = ShapeSerializer::class)
|
||||||
|
data class Shape(
|
||||||
|
/**
|
||||||
|
* The style of the corners.
|
||||||
|
* null to inherit the corner style from the base shape.
|
||||||
|
*/
|
||||||
|
val corners: CornerStyle? = null,
|
||||||
|
/**
|
||||||
|
* Radii in dp, in the order of top-start, top-end, bottom-end, bottom-start.
|
||||||
|
* null to inherit the radius from the base shape.
|
||||||
|
*/
|
||||||
|
val radii: IntArray? = null,
|
||||||
|
) {
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other !is Shape) return false
|
||||||
|
return corners == other.corners &&
|
||||||
|
radii.contentEquals(other.radii)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = corners.hashCode()
|
||||||
|
result = 31 * result + radii.contentHashCode()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
val type = when (corners) {
|
||||||
|
CornerStyle.Rounded -> "r"
|
||||||
|
CornerStyle.Cut -> "c"
|
||||||
|
null -> "$"
|
||||||
|
}
|
||||||
|
val radii = radii?.joinToString("|") ?: ""
|
||||||
|
return "$type.${radii}"
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromString(string: String?): Shape? {
|
||||||
|
|
||||||
|
if (string == null) return null
|
||||||
|
val parts = string.split('.')
|
||||||
|
val corners = when (parts[0]) {
|
||||||
|
"r" -> CornerStyle.Rounded
|
||||||
|
"c" -> CornerStyle.Cut
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
val radii = if (parts.size > 1 && parts[1].isNotEmpty()) {
|
||||||
|
parts[1].split("|").map { it.toInt() }.toIntArray()
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
return Shape(corners, radii)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class CornerStyle {
|
||||||
|
Rounded,
|
||||||
|
Cut,
|
||||||
|
}
|
||||||
@ -4,7 +4,8 @@ import android.content.Context
|
|||||||
import de.mm20.launcher2.backup.Backupable
|
import de.mm20.launcher2.backup.Backupable
|
||||||
import de.mm20.launcher2.crashreporter.CrashReporter
|
import de.mm20.launcher2.crashreporter.CrashReporter
|
||||||
import de.mm20.launcher2.database.AppDatabase
|
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.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@ -16,7 +17,6 @@ import kotlinx.coroutines.flow.map
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.serialization.SerializationException
|
import kotlinx.serialization.SerializationException
|
||||||
import kotlinx.serialization.encodeToString
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
@ -26,50 +26,50 @@ class ThemeRepository(
|
|||||||
) : Backupable {
|
) : Backupable {
|
||||||
private val scope = CoroutineScope(Dispatchers.IO + Job())
|
private val scope = CoroutineScope(Dispatchers.IO + Job())
|
||||||
|
|
||||||
fun getThemes(): Flow<List<Theme>> {
|
fun getAllColors(): Flow<List<Colors>> {
|
||||||
return database.themeDao().getAll().map {
|
return database.themeDao().getAllColors().map {
|
||||||
getBuiltInThemes() + it.map { Theme(it) }
|
getBuiltInColors() + it.map { Colors(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getTheme(id: UUID): Flow<Theme?> {
|
fun getColors(id: UUID): Flow<Colors?> {
|
||||||
if (id == DefaultThemeId) return flowOf(getDefaultTheme())
|
if (id == DefaultThemeId) return flowOf(getDefaultColors())
|
||||||
if (id == BlackAndWhiteThemeId) return flowOf(getBlackAndWhiteTheme())
|
if (id == BlackAndWhiteThemeId) return flowOf(getBlackAndWhiteColors())
|
||||||
return database.themeDao().get(id).map { it?.let { Theme(it) } }.flowOn(Dispatchers.Default)
|
return database.themeDao().getColors(id).map { it?.let { Colors(it) } }.flowOn(Dispatchers.Default)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createTheme(theme: Theme) {
|
fun createColors(colors: Colors) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
database.themeDao().insert(theme.toEntity())
|
database.themeDao().insertColors(colors.toEntity())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateTheme(theme: Theme) {
|
fun updateColors(colors: Colors) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
database.themeDao().update(theme.toEntity())
|
database.themeDao().updateColors(colors.toEntity())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getThemeOrDefault(theme: ThemeDescriptor?): Flow<Theme> {
|
fun getColorsOrDefault(theme: ColorsDescriptor?): Flow<Colors> {
|
||||||
return when(theme) {
|
return when(theme) {
|
||||||
is ThemeDescriptor.BlackAndWhite -> flowOf(getBlackAndWhiteTheme())
|
is ColorsDescriptor.BlackAndWhite -> flowOf(getBlackAndWhiteColors())
|
||||||
is ThemeDescriptor.Custom -> {
|
is ColorsDescriptor.Custom -> {
|
||||||
val id = UUID.fromString(theme.id)
|
val id = UUID.fromString(theme.id)
|
||||||
getTheme(id).map { it ?: getDefaultTheme() }
|
getColors(id).map { it ?: getDefaultColors() }
|
||||||
}
|
}
|
||||||
else -> flowOf(getDefaultTheme())
|
else -> flowOf(getDefaultColors())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getBuiltInThemes(): List<Theme> {
|
private fun getBuiltInColors(): List<Colors> {
|
||||||
return listOf(
|
return listOf(
|
||||||
getDefaultTheme(),
|
getDefaultColors(),
|
||||||
getBlackAndWhiteTheme(),
|
getBlackAndWhiteColors(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getDefaultTheme(): Theme {
|
private fun getDefaultColors(): Colors {
|
||||||
return Theme(
|
return Colors(
|
||||||
id = DefaultThemeId,
|
id = DefaultThemeId,
|
||||||
builtIn = true,
|
builtIn = true,
|
||||||
name = context.getString(R.string.preference_colors_default),
|
name = context.getString(R.string.preference_colors_default),
|
||||||
@ -79,8 +79,8 @@ class ThemeRepository(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getBlackAndWhiteTheme(): Theme {
|
private fun getBlackAndWhiteColors(): Colors {
|
||||||
return Theme(
|
return Colors(
|
||||||
id = BlackAndWhiteThemeId,
|
id = BlackAndWhiteThemeId,
|
||||||
builtIn = true,
|
builtIn = true,
|
||||||
name = context.getString(R.string.preference_colors_bw),
|
name = context.getString(R.string.preference_colors_bw),
|
||||||
@ -90,16 +90,127 @@ class ThemeRepository(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteTheme(theme: Theme) {
|
fun deleteColors(colors: Colors) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
database.themeDao().delete(theme.id)
|
database.themeDao().deleteColors(colors.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAllShapes(): Flow<List<Shapes>> {
|
||||||
|
return database.themeDao().getAllShapes().map {
|
||||||
|
getBuiltInShapes() + it.map { Shapes(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getShapes(id: UUID): Flow<Shapes?> {
|
||||||
|
if (id == DefaultThemeId) return flowOf(getDefaultShapes())
|
||||||
|
if (id == ExtraRoundShapesId) return flowOf(getExtraRoundShapes())
|
||||||
|
if (id == RectShapesId) return flowOf(getRectShapes())
|
||||||
|
if (id == CutShapesId) return flowOf(getCutShapes())
|
||||||
|
return database.themeDao().getShapes(id).map { it?.let { Shapes(it) } }.flowOn(Dispatchers.Default)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createShapes(shapes: Shapes) {
|
||||||
|
scope.launch {
|
||||||
|
database.themeDao().insertShapes(shapes.toEntity())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateShapes(shapes: Shapes) {
|
||||||
|
scope.launch {
|
||||||
|
database.themeDao().updateShapes(shapes.toEntity())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getShapesOrDefault(theme: ShapesDescriptor?): Flow<Shapes> {
|
||||||
|
return when(theme) {
|
||||||
|
is ShapesDescriptor.Custom -> {
|
||||||
|
val id = UUID.fromString(theme.id)
|
||||||
|
getShapes(id).map { it ?: getDefaultShapes() }
|
||||||
|
}
|
||||||
|
is ShapesDescriptor.ExtraRound -> flowOf(getExtraRoundShapes())
|
||||||
|
is ShapesDescriptor.Rect -> flowOf(getRectShapes())
|
||||||
|
is ShapesDescriptor.Cut -> flowOf(getCutShapes())
|
||||||
|
else -> flowOf(getDefaultShapes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getBuiltInShapes(): List<Shapes> {
|
||||||
|
return listOf(
|
||||||
|
getDefaultShapes(),
|
||||||
|
getExtraRoundShapes(),
|
||||||
|
getRectShapes(),
|
||||||
|
getCutShapes(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getDefaultShapes(): Shapes {
|
||||||
|
return Shapes(
|
||||||
|
id = DefaultThemeId,
|
||||||
|
builtIn = true,
|
||||||
|
name = context.getString(R.string.preference_shapes_default),
|
||||||
|
baseShape = Shape(
|
||||||
|
corners = CornerStyle.Rounded,
|
||||||
|
radii = intArrayOf(12, 12, 12, 12),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCutShapes(): Shapes {
|
||||||
|
return Shapes(
|
||||||
|
id = CutShapesId,
|
||||||
|
builtIn = true,
|
||||||
|
name = context.getString(R.string.preference_cards_shape_cut),
|
||||||
|
baseShape = Shape(
|
||||||
|
corners = CornerStyle.Cut,
|
||||||
|
radii = intArrayOf(12, 12, 12, 12),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getExtraRoundShapes(): Shapes {
|
||||||
|
return Shapes(
|
||||||
|
id = ExtraRoundShapesId,
|
||||||
|
builtIn = true,
|
||||||
|
name = context.getString(R.string.preference_shapes_extra_round),
|
||||||
|
baseShape = Shape(
|
||||||
|
corners = CornerStyle.Rounded,
|
||||||
|
radii = intArrayOf(24, 24, 24, 24),
|
||||||
|
),
|
||||||
|
extraLarge = Shape(
|
||||||
|
radii = intArrayOf(36, 36, 36, 36),
|
||||||
|
),
|
||||||
|
extraLargeIncreased = Shape(
|
||||||
|
radii = intArrayOf(40, 40, 40, 40),
|
||||||
|
),
|
||||||
|
extraExtraLarge = Shape(
|
||||||
|
radii = intArrayOf(56, 56, 56, 56),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getRectShapes(): Shapes {
|
||||||
|
return Shapes(
|
||||||
|
id = RectShapesId,
|
||||||
|
builtIn = true,
|
||||||
|
name = context.getString(R.string.preference_shapes_rect),
|
||||||
|
baseShape = Shape(
|
||||||
|
corners = CornerStyle.Rounded,
|
||||||
|
radii = intArrayOf(0, 0, 0, 0),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteShapes(shapes: Shapes) {
|
||||||
|
scope.launch {
|
||||||
|
database.themeDao().deleteShapes(shapes.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun backup(toDir: File) = withContext(Dispatchers.IO) {
|
override suspend fun backup(toDir: File) = withContext(Dispatchers.IO) {
|
||||||
val dao = database.themeDao()
|
val dao = database.themeDao()
|
||||||
val themes = dao.getAll().first().map { Theme(it) }
|
val colors = dao.getAllColors().first().map { Colors(it) }
|
||||||
val data = ThemeJson.encodeToString(themes)
|
val data = LegacyThemeJson.encodeToString(colors)
|
||||||
|
|
||||||
val file = File(toDir, "themes.0000")
|
val file = File(toDir, "themes.0000")
|
||||||
file.bufferedWriter().use {
|
file.bufferedWriter().use {
|
||||||
@ -109,7 +220,7 @@ class ThemeRepository(
|
|||||||
|
|
||||||
override suspend fun restore(fromDir: File) = withContext(Dispatchers.IO) {
|
override suspend fun restore(fromDir: File) = withContext(Dispatchers.IO) {
|
||||||
val dao = database.themeDao()
|
val dao = database.themeDao()
|
||||||
dao.deleteAll()
|
dao.deleteAllColors()
|
||||||
|
|
||||||
val files =
|
val files =
|
||||||
fromDir.listFiles { _, name -> name.startsWith("themes.") }
|
fromDir.listFiles { _, name -> name.startsWith("themes.") }
|
||||||
@ -117,8 +228,8 @@ class ThemeRepository(
|
|||||||
|
|
||||||
for (file in files) {
|
for (file in files) {
|
||||||
val data = file.inputStream().reader().readText()
|
val data = file.inputStream().reader().readText()
|
||||||
val themes: List<Theme> = try {
|
val colors: List<Colors> = try {
|
||||||
ThemeJson.decodeFromString(data)
|
LegacyThemeJson.decodeFromString(data)
|
||||||
} catch (e: SerializationException) {
|
} catch (e: SerializationException) {
|
||||||
CrashReporter.logException(e)
|
CrashReporter.logException(e)
|
||||||
continue
|
continue
|
||||||
@ -126,7 +237,7 @@ class ThemeRepository(
|
|||||||
CrashReporter.logException(e)
|
CrashReporter.logException(e)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
dao.insertAll(themes.map { it.toEntity() })
|
dao.insertAllColors(colors.map { it.toEntity() })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user