Add import for new color scheme format
This commit is contained in:
parent
f21feba000
commit
889aa37915
@ -1,6 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.android.library)
|
alias(libs.plugins.android.library)
|
||||||
alias(libs.plugins.kotlin.android)
|
alias(libs.plugins.kotlin.android)
|
||||||
|
alias(libs.plugins.kotlin.plugin.serialization)
|
||||||
alias(libs.plugins.kotlin.plugin.compose)
|
alias(libs.plugins.kotlin.plugin.compose)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -25,7 +25,6 @@ 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.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
@ -46,7 +45,6 @@ import androidx.compose.ui.unit.dp
|
|||||||
import de.mm20.launcher2.preferences.SearchBarStyle
|
import de.mm20.launcher2.preferences.SearchBarStyle
|
||||||
import de.mm20.launcher2.ui.R
|
import de.mm20.launcher2.ui.R
|
||||||
import de.mm20.launcher2.ui.layout.BottomReversed
|
import de.mm20.launcher2.ui.layout.BottomReversed
|
||||||
import de.mm20.launcher2.ui.locals.LocalCardStyle
|
|
||||||
import de.mm20.launcher2.ui.theme.transparency.LocalTransparencyScheme
|
import de.mm20.launcher2.ui.theme.transparency.LocalTransparencyScheme
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -61,6 +59,7 @@ fun SearchBar(
|
|||||||
onUnfocus: () -> Unit = {},
|
onUnfocus: () -> Unit = {},
|
||||||
reverse: Boolean = false,
|
reverse: Boolean = false,
|
||||||
darkColors: Boolean = false,
|
darkColors: Boolean = false,
|
||||||
|
readOnly: Boolean = false,
|
||||||
menu: @Composable RowScope.() -> Unit = {},
|
menu: @Composable RowScope.() -> Unit = {},
|
||||||
actions: @Composable ColumnScope.() -> Unit = {},
|
actions: @Composable ColumnScope.() -> Unit = {},
|
||||||
onKeyboardActionGo: (KeyboardActionScope.() -> Unit)? = null
|
onKeyboardActionGo: (KeyboardActionScope.() -> Unit)? = null
|
||||||
@ -190,7 +189,8 @@ fun SearchBar(
|
|||||||
),
|
),
|
||||||
keyboardActions = KeyboardActions(
|
keyboardActions = KeyboardActions(
|
||||||
onGo = onKeyboardActionGo,
|
onGo = onKeyboardActionGo,
|
||||||
)
|
),
|
||||||
|
readOnly = readOnly,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Row(
|
Row(
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import de.mm20.launcher2.ui.base.BaseActivity
|
|||||||
import de.mm20.launcher2.ui.base.ProvideSettings
|
import de.mm20.launcher2.ui.base.ProvideSettings
|
||||||
import de.mm20.launcher2.ui.common.ImportThemeSheet
|
import de.mm20.launcher2.ui.common.ImportThemeSheet
|
||||||
import de.mm20.launcher2.ui.overlays.OverlayHost
|
import de.mm20.launcher2.ui.overlays.OverlayHost
|
||||||
|
import de.mm20.launcher2.ui.settings.appearance.ImportThemeSettingsScreen
|
||||||
import de.mm20.launcher2.ui.theme.LauncherTheme
|
import de.mm20.launcher2.ui.theme.LauncherTheme
|
||||||
|
|
||||||
class ImportThemeActivity : BaseActivity() {
|
class ImportThemeActivity : BaseActivity() {
|
||||||
@ -19,10 +20,7 @@ class ImportThemeActivity : BaseActivity() {
|
|||||||
LauncherTheme {
|
LauncherTheme {
|
||||||
ProvideSettings {
|
ProvideSettings {
|
||||||
OverlayHost {
|
OverlayHost {
|
||||||
ImportThemeSheet(
|
ImportThemeSettingsScreen(uri)
|
||||||
onDismiss = { finish() },
|
|
||||||
uri = uri,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package de.mm20.launcher2.ui.settings
|
package de.mm20.launcher2.ui.settings
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.SystemBarStyle
|
import androidx.activity.SystemBarStyle
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
@ -22,11 +23,13 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.toArgb
|
import androidx.compose.ui.graphics.toArgb
|
||||||
|
import androidx.core.net.toUri
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import androidx.navigation.navArgument
|
import androidx.navigation.navArgument
|
||||||
|
import androidx.navigation.toRoute
|
||||||
import de.mm20.launcher2.licenses.AppLicense
|
import de.mm20.launcher2.licenses.AppLicense
|
||||||
import de.mm20.launcher2.licenses.OpenSourceLicenses
|
import de.mm20.launcher2.licenses.OpenSourceLicenses
|
||||||
import de.mm20.launcher2.ui.base.BaseActivity
|
import de.mm20.launcher2.ui.base.BaseActivity
|
||||||
@ -38,6 +41,8 @@ import de.mm20.launcher2.ui.overlays.OverlayHost
|
|||||||
import de.mm20.launcher2.ui.settings.about.AboutSettingsScreen
|
import de.mm20.launcher2.ui.settings.about.AboutSettingsScreen
|
||||||
import de.mm20.launcher2.ui.settings.appearance.AppearanceSettingsScreen
|
import de.mm20.launcher2.ui.settings.appearance.AppearanceSettingsScreen
|
||||||
import de.mm20.launcher2.ui.settings.appearance.ExportThemeSettingsScreen
|
import de.mm20.launcher2.ui.settings.appearance.ExportThemeSettingsScreen
|
||||||
|
import de.mm20.launcher2.ui.settings.appearance.ImportThemeSettingsRoute
|
||||||
|
import de.mm20.launcher2.ui.settings.appearance.ImportThemeSettingsScreen
|
||||||
import de.mm20.launcher2.ui.settings.backup.BackupSettingsScreen
|
import de.mm20.launcher2.ui.settings.backup.BackupSettingsScreen
|
||||||
import de.mm20.launcher2.ui.settings.breezyweather.BreezyWeatherSettingsScreen
|
import de.mm20.launcher2.ui.settings.breezyweather.BreezyWeatherSettingsScreen
|
||||||
import de.mm20.launcher2.ui.settings.buildinfo.BuildInfoSettingsScreen
|
import de.mm20.launcher2.ui.settings.buildinfo.BuildInfoSettingsScreen
|
||||||
@ -163,6 +168,10 @@ class SettingsActivity : BaseActivity() {
|
|||||||
composable("settings/appearance/export") {
|
composable("settings/appearance/export") {
|
||||||
ExportThemeSettingsScreen()
|
ExportThemeSettingsScreen()
|
||||||
}
|
}
|
||||||
|
composable<ImportThemeSettingsRoute> {
|
||||||
|
val route: ImportThemeSettingsRoute = it.toRoute() ?: return@composable
|
||||||
|
ImportThemeSettingsScreen(route.fromUri.toUri())
|
||||||
|
}
|
||||||
composable("settings/homescreen") {
|
composable("settings/homescreen") {
|
||||||
HomescreenSettingsScreen()
|
HomescreenSettingsScreen()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
package de.mm20.launcher2.ui.settings.appearance
|
package de.mm20.launcher2.ui.settings.appearance
|
||||||
|
|
||||||
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.rounded.ArrowCircleDown
|
import androidx.compose.material.icons.rounded.ArrowCircleDown
|
||||||
import androidx.compose.material.icons.rounded.ArrowCircleUp
|
import androidx.compose.material.icons.rounded.ArrowCircleUp
|
||||||
@ -35,6 +37,14 @@ fun AppearanceSettingsScreen() {
|
|||||||
val colorThemeName by viewModel.colorThemeName.collectAsStateWithLifecycle(null)
|
val colorThemeName by viewModel.colorThemeName.collectAsStateWithLifecycle(null)
|
||||||
val shapeThemeName by viewModel.shapeThemeName.collectAsStateWithLifecycle(null)
|
val shapeThemeName by viewModel.shapeThemeName.collectAsStateWithLifecycle(null)
|
||||||
val compatModeColors by viewModel.compatModeColors.collectAsState()
|
val compatModeColors by viewModel.compatModeColors.collectAsState()
|
||||||
|
|
||||||
|
val importLauncher = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) {
|
||||||
|
if (it == null) {
|
||||||
|
return@rememberLauncherForActivityResult
|
||||||
|
}
|
||||||
|
navController?.navigate(ImportThemeSettingsRoute(it.toString()))
|
||||||
|
}
|
||||||
|
|
||||||
PreferenceScreen(title = stringResource(id = R.string.preference_screen_appearance)) {
|
PreferenceScreen(title = stringResource(id = R.string.preference_screen_appearance)) {
|
||||||
item {
|
item {
|
||||||
PreferenceCategory {
|
PreferenceCategory {
|
||||||
@ -105,11 +115,14 @@ fun AppearanceSettingsScreen() {
|
|||||||
item {
|
item {
|
||||||
PreferenceCategory {
|
PreferenceCategory {
|
||||||
Preference(
|
Preference(
|
||||||
title = "Import",
|
title = stringResource(R.string.theme_import_title),
|
||||||
icon = Icons.Rounded.ArrowCircleDown,
|
icon = Icons.Rounded.ArrowCircleDown,
|
||||||
|
onClick = {
|
||||||
|
importLauncher.launch(arrayOf("*/*"))
|
||||||
|
}
|
||||||
)
|
)
|
||||||
Preference(
|
Preference(
|
||||||
title = "Export",
|
title = stringResource(R.string.theme_export_title),
|
||||||
icon = Icons.Rounded.ArrowCircleUp,
|
icon = Icons.Rounded.ArrowCircleUp,
|
||||||
onClick = {
|
onClick = {
|
||||||
navController?.navigate("settings/appearance/export")
|
navController?.navigate("settings/appearance/export")
|
||||||
|
|||||||
@ -0,0 +1,350 @@
|
|||||||
|
package de.mm20.launcher2.ui.settings.appearance
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.activity.compose.LocalActivity
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.ChangeCircle
|
||||||
|
import androidx.compose.material.icons.rounded.CropSquare
|
||||||
|
import androidx.compose.material.icons.rounded.DarkMode
|
||||||
|
import androidx.compose.material.icons.rounded.Edit
|
||||||
|
import androidx.compose.material.icons.rounded.ErrorOutline
|
||||||
|
import androidx.compose.material.icons.rounded.LightMode
|
||||||
|
import androidx.compose.material.icons.rounded.MoreVert
|
||||||
|
import androidx.compose.material.icons.rounded.Palette
|
||||||
|
import androidx.compose.material.icons.rounded.PublishedWithChanges
|
||||||
|
import androidx.compose.material.icons.rounded.Star
|
||||||
|
import androidx.compose.material.icons.rounded.Upgrade
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.FilledTonalIconButton
|
||||||
|
import androidx.compose.material3.FilterChip
|
||||||
|
import androidx.compose.material3.FilterChipDefaults
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedButton
|
||||||
|
import androidx.compose.material3.SegmentedButton
|
||||||
|
import androidx.compose.material3.SegmentedButtonDefaults
|
||||||
|
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.draw.innerShadow
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.shadow.InnerShadow
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import de.mm20.launcher2.preferences.SearchBarStyle
|
||||||
|
import de.mm20.launcher2.ui.R
|
||||||
|
import de.mm20.launcher2.ui.component.Banner
|
||||||
|
import de.mm20.launcher2.ui.component.SearchBar
|
||||||
|
import de.mm20.launcher2.ui.component.SearchBarLevel
|
||||||
|
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.component.preferences.SwitchPreference
|
||||||
|
import de.mm20.launcher2.ui.locals.LocalDarkTheme
|
||||||
|
import de.mm20.launcher2.ui.theme.colorscheme.darkColorSchemeOf
|
||||||
|
import de.mm20.launcher2.ui.theme.colorscheme.lightColorSchemeOf
|
||||||
|
import de.mm20.launcher2.ui.theme.shapes.shapesOf
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ImportThemeSettingsRoute(val fromUri: String)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ImportThemeSettingsScreen(
|
||||||
|
fromUri: Uri,
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val activity = LocalActivity.current
|
||||||
|
val viewModel: ImportThemeSettingsScreenVM = viewModel()
|
||||||
|
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
val themeBundle = viewModel.themeBundle
|
||||||
|
|
||||||
|
LaunchedEffect(fromUri) {
|
||||||
|
viewModel.init(context, fromUri)
|
||||||
|
}
|
||||||
|
|
||||||
|
PreferenceScreen(title = stringResource(R.string.theme_import_title)) {
|
||||||
|
|
||||||
|
if (viewModel.error) {
|
||||||
|
item {
|
||||||
|
PreferenceCategory {
|
||||||
|
Banner(
|
||||||
|
text = stringResource(R.string.import_theme_error),
|
||||||
|
icon = Icons.Rounded.ErrorOutline,
|
||||||
|
modifier = Modifier
|
||||||
|
.background(MaterialTheme.colorScheme.surface)
|
||||||
|
.padding(16.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (themeBundle != null) {
|
||||||
|
item {
|
||||||
|
PreferenceCategory {
|
||||||
|
Preference(
|
||||||
|
title = themeBundle.name,
|
||||||
|
summary = themeBundle.author?.takeIf { it.isNotBlank() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
val isDarkMode = LocalDarkTheme.current
|
||||||
|
var darkModePreview by remember { mutableStateOf(isDarkMode) }
|
||||||
|
MaterialTheme(
|
||||||
|
colorScheme = themeBundle.colors?.let {
|
||||||
|
if (darkModePreview) darkColorSchemeOf(it) else lightColorSchemeOf(it)
|
||||||
|
} ?: MaterialTheme.colorScheme,
|
||||||
|
shapes = themeBundle.shapes?.let { shapesOf(it) } ?: MaterialTheme.shapes,
|
||||||
|
) {
|
||||||
|
ThemePreview(
|
||||||
|
darkMode = darkModePreview,
|
||||||
|
onDarkModeChanged = { darkModePreview = it }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
PreferenceCategory {
|
||||||
|
if (themeBundle.colors != null) {
|
||||||
|
Preference(
|
||||||
|
icon = Icons.Rounded.Palette,
|
||||||
|
title = stringResource(R.string.preference_screen_colors),
|
||||||
|
summary = themeBundle.colors?.name,
|
||||||
|
controls = if (viewModel.colorsExists) {
|
||||||
|
{
|
||||||
|
Icon(Icons.Rounded.ChangeCircle, null)
|
||||||
|
}
|
||||||
|
} else null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (themeBundle.shapes != null) {
|
||||||
|
Preference(
|
||||||
|
icon = Icons.Rounded.CropSquare,
|
||||||
|
title = stringResource(R.string.preference_screen_shapes),
|
||||||
|
summary = themeBundle.shapes?.name,
|
||||||
|
controls = if (viewModel.shapesExists) {
|
||||||
|
{
|
||||||
|
Icon(Icons.Rounded.ChangeCircle, null)
|
||||||
|
}
|
||||||
|
} else null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (viewModel.colorsExists || viewModel.shapesExists) {
|
||||||
|
Banner(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(
|
||||||
|
MaterialTheme.colorScheme.surface,
|
||||||
|
MaterialTheme.shapes.extraSmall
|
||||||
|
)
|
||||||
|
.padding(16.dp),
|
||||||
|
icon = Icons.Rounded.ChangeCircle,
|
||||||
|
text = stringResource(R.string.import_theme_exists)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
PreferenceCategory {
|
||||||
|
SwitchPreference(
|
||||||
|
title = stringResource(R.string.import_theme_apply),
|
||||||
|
value = viewModel.applyTheme,
|
||||||
|
onValueChanged = { viewModel.applyTheme = it },
|
||||||
|
)
|
||||||
|
Button(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(
|
||||||
|
MaterialTheme.colorScheme.surface,
|
||||||
|
MaterialTheme.shapes.extraSmall
|
||||||
|
)
|
||||||
|
.padding(16.dp),
|
||||||
|
enabled = !viewModel.loading,
|
||||||
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
viewModel.import()?.join()
|
||||||
|
activity?.onBackPressed()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.action_import))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ThemePreview(
|
||||||
|
darkMode: Boolean,
|
||||||
|
onDarkModeChanged: (Boolean) -> Unit,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(MaterialTheme.shapes.medium)
|
||||||
|
.background(
|
||||||
|
MaterialTheme.colorScheme.surfaceContainer,
|
||||||
|
MaterialTheme.shapes.medium
|
||||||
|
)
|
||||||
|
.innerShadow(
|
||||||
|
MaterialTheme.shapes.medium,
|
||||||
|
InnerShadow(8.dp, color = Color(0f, 0f, 0f, 0.2f))
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
SearchBar(
|
||||||
|
style = SearchBarStyle.Solid,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = 12.dp, start = 12.dp, end = 12.dp),
|
||||||
|
level = SearchBarLevel.Active,
|
||||||
|
value = "",
|
||||||
|
onValueChange = {},
|
||||||
|
readOnly = true,
|
||||||
|
menu = {
|
||||||
|
IconButton(onClick = {}) {
|
||||||
|
Icon(Icons.Rounded.MoreVert, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = 12.dp, start = 12.dp, end = 12.dp)
|
||||||
|
.background(MaterialTheme.colorScheme.surface, MaterialTheme.shapes.medium)
|
||||||
|
.padding(12.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(80.dp)
|
||||||
|
.background(
|
||||||
|
MaterialTheme.colorScheme.secondaryContainer,
|
||||||
|
MaterialTheme.shapes.small
|
||||||
|
),
|
||||||
|
)
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"Title",
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
"Subtitle",
|
||||||
|
style = MaterialTheme.typography.titleSmall,
|
||||||
|
color = MaterialTheme.colorScheme.secondary
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
"Body",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
modifier = Modifier.padding(top = 4.dp),
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = 16.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
) {
|
||||||
|
FilterChip(
|
||||||
|
selected = true,
|
||||||
|
label = { Text("Chip") },
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
Icons.Rounded.Star, null,
|
||||||
|
modifier = Modifier.size(FilterChipDefaults.IconSize)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onClick = { },
|
||||||
|
)
|
||||||
|
FilterChip(
|
||||||
|
selected = false,
|
||||||
|
label = { Text("Chip") },
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
Icons.Rounded.Star, null,
|
||||||
|
modifier = Modifier.size(FilterChipDefaults.IconSize)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onClick = { },
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
FilledTonalIconButton(onClick = {}) {
|
||||||
|
Icon(Icons.Rounded.Edit, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HorizontalDivider(
|
||||||
|
modifier = Modifier.padding(vertical = 8.dp)
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.End),
|
||||||
|
) {
|
||||||
|
Button(onClick = {}) { Text("Button") }
|
||||||
|
OutlinedButton(onClick = {}) { Text("Button") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(12.dp),
|
||||||
|
contentAlignment = Alignment.CenterEnd,
|
||||||
|
) {
|
||||||
|
SingleChoiceSegmentedButtonRow {
|
||||||
|
SegmentedButton(
|
||||||
|
shape = SegmentedButtonDefaults.itemShape(index = 0, count = 2),
|
||||||
|
selected = !darkMode,
|
||||||
|
onClick = { onDarkModeChanged(false) }
|
||||||
|
) {
|
||||||
|
Icon(Icons.Rounded.LightMode, null)
|
||||||
|
}
|
||||||
|
SegmentedButton(
|
||||||
|
shape = SegmentedButtonDefaults.itemShape(index = 1, count = 2),
|
||||||
|
selected = darkMode,
|
||||||
|
onClick = { onDarkModeChanged(true) }
|
||||||
|
) {
|
||||||
|
Icon(Icons.Rounded.DarkMode, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun ThemePreviewPreview() {
|
||||||
|
ThemePreview(
|
||||||
|
darkMode = false,
|
||||||
|
onDarkModeChanged = {}
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,109 @@
|
|||||||
|
package de.mm20.launcher2.ui.settings.appearance
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import de.mm20.launcher2.crashreporter.CrashReporter
|
||||||
|
import de.mm20.launcher2.preferences.ColorsDescriptor
|
||||||
|
import de.mm20.launcher2.preferences.ShapesDescriptor
|
||||||
|
import de.mm20.launcher2.preferences.ui.UiSettings
|
||||||
|
import de.mm20.launcher2.themes.ThemeBundle
|
||||||
|
import de.mm20.launcher2.themes.ThemeRepository
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.koin.core.component.KoinComponent
|
||||||
|
import org.koin.core.component.inject
|
||||||
|
import kotlin.getValue
|
||||||
|
|
||||||
|
class ImportThemeSettingsScreenVM: ViewModel(), KoinComponent {
|
||||||
|
|
||||||
|
private val themeRepository by inject<ThemeRepository>()
|
||||||
|
private val uiSettings by inject<UiSettings>()
|
||||||
|
|
||||||
|
var themeBundle by mutableStateOf<ThemeBundle?>(null)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var colorsExists by mutableStateOf(false)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var shapesExists by mutableStateOf(false)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var loading by mutableStateOf(false)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var error by mutableStateOf(false)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var applyTheme by mutableStateOf(true)
|
||||||
|
|
||||||
|
fun init(context: Context, fromUri: Uri) {
|
||||||
|
themeBundle = null
|
||||||
|
error = false
|
||||||
|
applyTheme = true
|
||||||
|
loading = true
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
context.contentResolver.openInputStream(fromUri)?.reader()?.use {
|
||||||
|
val text = it.readText()
|
||||||
|
val theme = ThemeBundle.fromJson(text)
|
||||||
|
if (theme != null) {
|
||||||
|
val colors = theme.colors?.id?.let { themeRepository.getColors(it) }?.first()
|
||||||
|
val shapes = theme.shapes?.id?.let { themeRepository.getShapes(it) }?.first()
|
||||||
|
|
||||||
|
colorsExists = colors != null
|
||||||
|
shapesExists = shapes != null
|
||||||
|
themeBundle = theme
|
||||||
|
loading = false
|
||||||
|
} else {
|
||||||
|
error = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: SecurityException) {
|
||||||
|
CrashReporter.logException(e)
|
||||||
|
error = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun import(): Job? {
|
||||||
|
val themeBundle = this.themeBundle ?: return null
|
||||||
|
|
||||||
|
val colors = themeBundle.colors
|
||||||
|
val shapes = themeBundle.shapes
|
||||||
|
|
||||||
|
val colorsExist = this.colorsExists
|
||||||
|
val shapesExist = this.shapesExists
|
||||||
|
|
||||||
|
loading = true
|
||||||
|
return viewModelScope.launch {
|
||||||
|
if (colors != null) {
|
||||||
|
if (colorsExist) {
|
||||||
|
themeRepository.updateColors(colors)
|
||||||
|
} else {
|
||||||
|
themeRepository.createColors(colors)
|
||||||
|
}
|
||||||
|
if (applyTheme) {
|
||||||
|
uiSettings.setColors(ColorsDescriptor.Custom(colors.id.toString()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (shapes != null) {
|
||||||
|
if (shapesExist) {
|
||||||
|
themeRepository.updateShapes(shapes)
|
||||||
|
} else {
|
||||||
|
themeRepository.createShapes(shapes)
|
||||||
|
}
|
||||||
|
if (applyTheme) {
|
||||||
|
uiSettings.setShapes(ShapesDescriptor.Custom(shapes.id.toString()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -97,7 +97,8 @@ fun CorePaletteColorPreference(
|
|||||||
value = currentValue == null,
|
value = currentValue == null,
|
||||||
onValueChanged = {
|
onValueChanged = {
|
||||||
currentValue = if (it) null else defaultValue
|
currentValue = if (it) null else defaultValue
|
||||||
}
|
},
|
||||||
|
containerColor = Color.Transparent,
|
||||||
)
|
)
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
currentValue != null,
|
currentValue != null,
|
||||||
|
|||||||
@ -0,0 +1,22 @@
|
|||||||
|
package de.mm20.launcher2.serialization
|
||||||
|
|
||||||
|
import androidx.core.graphics.toColorInt
|
||||||
|
import kotlinx.serialization.KSerializer
|
||||||
|
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
|
||||||
|
|
||||||
|
object ColorIntAsHexSerializer : KSerializer<Int> {
|
||||||
|
override val descriptor: SerialDescriptor =
|
||||||
|
PrimitiveSerialDescriptor(javaClass.canonicalName!!, PrimitiveKind.STRING)
|
||||||
|
|
||||||
|
override fun deserialize(decoder: Decoder): Int {
|
||||||
|
return decoder.decodeString().toColorInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun serialize(encoder: Encoder, value: Int) {
|
||||||
|
encoder.encodeString("#" + value.toUInt().toString(16).padStart(8, '0'))
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
package de.mm20.launcher2.serialization
|
||||||
|
|
||||||
|
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 java.util.UUID
|
||||||
|
|
||||||
|
object UUIDSerializer: KSerializer<UUID> {
|
||||||
|
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor(
|
||||||
|
"UUIDSerializer",
|
||||||
|
PrimitiveKind.STRING
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun serialize(encoder: Encoder, value: UUID) {
|
||||||
|
encoder.encodeString(value.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deserialize(decoder: Decoder): UUID {
|
||||||
|
val string = decoder.decodeString()
|
||||||
|
return try {
|
||||||
|
UUID.fromString(string)
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
throw SerializationException("Invalid UUID format: $string", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -828,6 +828,7 @@
|
|||||||
<string name="theme_color_scheme_custom_color">Custom</string>
|
<string name="theme_color_scheme_custom_color">Custom</string>
|
||||||
<string name="preference_restore_default">Restore default</string>
|
<string name="preference_restore_default">Restore default</string>
|
||||||
<string name="import_theme_apply">Apply theme</string>
|
<string name="import_theme_apply">Apply theme</string>
|
||||||
|
<string name="import_theme_exists">Theme already exists and will be updated</string>
|
||||||
<string name="import_theme_error">The selected file could not be read. Please make sure that you selected a valid theme file (*.kvtheme), and that the file is not corrupt.</string>
|
<string name="import_theme_error">The selected file could not be read. Please make sure that you selected a valid theme file (*.kvtheme), and that the file is not corrupt.</string>
|
||||||
<string name="shortcut_label_unavailable">Unavailable</string>
|
<string name="shortcut_label_unavailable">Unavailable</string>
|
||||||
<string name="app_label_locked_profile">Locked</string>
|
<string name="app_label_locked_profile">Locked</string>
|
||||||
@ -1032,6 +1033,7 @@
|
|||||||
<string name="bad_configuration_title">Congratulations, you\'ve locked yourself out!</string>
|
<string name="bad_configuration_title">Congratulations, you\'ve locked yourself out!</string>
|
||||||
<string name="bad_configuration_summary">You\'ve discovered a combination of settings that makes both the search and settings inaccessible — effectively locking you out of the launcher.</string>
|
<string name="bad_configuration_summary">You\'ve discovered a combination of settings that makes both the search and settings inaccessible — effectively locking you out of the launcher.</string>
|
||||||
<string name="theme_export_title">Export theme</string>
|
<string name="theme_export_title">Export theme</string>
|
||||||
|
<string name="theme_import_title">Import theme</string>
|
||||||
<string name="theme_bundle_name">Name</string>
|
<string name="theme_bundle_name">Name</string>
|
||||||
<string name="theme_bundle_author">Author</string>
|
<string name="theme_bundle_author">Author</string>
|
||||||
</resources>
|
</resources>
|
||||||
@ -1,14 +1,16 @@
|
|||||||
package de.mm20.launcher2.themes
|
package de.mm20.launcher2.themes
|
||||||
|
|
||||||
import de.mm20.launcher2.database.entities.ColorsEntity
|
import de.mm20.launcher2.database.entities.ColorsEntity
|
||||||
|
import de.mm20.launcher2.serialization.ColorIntAsHexSerializer
|
||||||
|
import de.mm20.launcher2.serialization.UUIDSerializer
|
||||||
import hct.Hct
|
import hct.Hct
|
||||||
|
import kotlinx.serialization.Contextual
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.Transient
|
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Colors(
|
data class Colors(
|
||||||
@Transient val id: UUID = UUID.randomUUID(),
|
@Serializable(with = UUIDSerializer::class) val id: UUID = UUID.randomUUID(),
|
||||||
val builtIn: Boolean = false,
|
val builtIn: Boolean = false,
|
||||||
val name: String,
|
val name: String,
|
||||||
val corePalette: PartialCorePalette = EmptyCorePalette,
|
val corePalette: PartialCorePalette = EmptyCorePalette,
|
||||||
@ -229,7 +231,6 @@ enum class CorePaletteColor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable(with = ColorSerializer::class)
|
|
||||||
sealed interface Color {
|
sealed interface Color {
|
||||||
companion object {
|
companion object {
|
||||||
fun fromString(string: String?): Color? {
|
fun fromString(string: String?): Color? {
|
||||||
@ -267,6 +268,8 @@ value class StaticColor(val color: Int) : Color {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typealias CorePaletteColorValue = @Serializable(with = ColorIntAsHexSerializer::class) Int
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class CorePalette<out T : Int?>(
|
data class CorePalette<out T : Int?>(
|
||||||
val primary: T,
|
val primary: T,
|
||||||
@ -277,50 +280,50 @@ data class CorePalette<out T : Int?>(
|
|||||||
val error: T,
|
val error: T,
|
||||||
)
|
)
|
||||||
|
|
||||||
val EmptyCorePalette = CorePalette<Int?>(null, null, null, null, null, null)
|
val EmptyCorePalette = CorePalette<CorePaletteColorValue?>(null, null, null, null, null, null)
|
||||||
|
|
||||||
typealias FullCorePalette = CorePalette<Int>
|
typealias FullCorePalette = CorePalette<CorePaletteColorValue>
|
||||||
typealias PartialCorePalette = CorePalette<Int?>
|
typealias PartialCorePalette = CorePalette<CorePaletteColorValue?>
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ColorScheme<out T : Color?>(
|
data class ColorScheme<out T : Color?>(
|
||||||
val primary: T,
|
@Contextual val primary: T,
|
||||||
val onPrimary: T,
|
@Contextual val onPrimary: T,
|
||||||
val primaryContainer: T,
|
@Contextual val primaryContainer: T,
|
||||||
val onPrimaryContainer: T,
|
@Contextual val onPrimaryContainer: T,
|
||||||
val secondary: T,
|
@Contextual val secondary: T,
|
||||||
val onSecondary: T,
|
@Contextual val onSecondary: T,
|
||||||
val secondaryContainer: T,
|
@Contextual val secondaryContainer: T,
|
||||||
val onSecondaryContainer: T,
|
@Contextual val onSecondaryContainer: T,
|
||||||
val tertiary: T,
|
@Contextual val tertiary: T,
|
||||||
val onTertiary: T,
|
@Contextual val onTertiary: T,
|
||||||
val tertiaryContainer: T,
|
@Contextual val tertiaryContainer: T,
|
||||||
val onTertiaryContainer: T,
|
@Contextual val onTertiaryContainer: T,
|
||||||
val error: T,
|
@Contextual val error: T,
|
||||||
val onError: T,
|
@Contextual val onError: T,
|
||||||
val errorContainer: T,
|
@Contextual val errorContainer: T,
|
||||||
val onErrorContainer: T,
|
@Contextual val onErrorContainer: T,
|
||||||
val surface: T,
|
@Contextual val surface: T,
|
||||||
val onSurface: T,
|
@Contextual val onSurface: T,
|
||||||
val onSurfaceVariant: T,
|
@Contextual val onSurfaceVariant: T,
|
||||||
val outline: T,
|
@Contextual val outline: T,
|
||||||
val outlineVariant: T,
|
@Contextual val outlineVariant: T,
|
||||||
val inverseSurface: T,
|
@Contextual val inverseSurface: T,
|
||||||
val inverseOnSurface: T,
|
@Contextual val inverseOnSurface: T,
|
||||||
val inversePrimary: T,
|
@Contextual val inversePrimary: T,
|
||||||
val surfaceDim: T,
|
@Contextual val surfaceDim: T,
|
||||||
val surfaceBright: T,
|
@Contextual val surfaceBright: T,
|
||||||
val surfaceContainerLowest: T,
|
@Contextual val surfaceContainerLowest: T,
|
||||||
val surfaceContainerLow: T,
|
@Contextual val surfaceContainerLow: T,
|
||||||
val surfaceContainer: T,
|
@Contextual val surfaceContainer: T,
|
||||||
val surfaceContainerHigh: T,
|
@Contextual val surfaceContainerHigh: T,
|
||||||
val surfaceContainerHighest: T,
|
@Contextual val surfaceContainerHighest: T,
|
||||||
|
|
||||||
val background: T,
|
@Contextual val background: T,
|
||||||
val onBackground: T,
|
@Contextual val onBackground: T,
|
||||||
val surfaceTint: T,
|
@Contextual val surfaceTint: T,
|
||||||
val scrim: T,
|
@Contextual val scrim: T,
|
||||||
val surfaceVariant: T,
|
@Contextual val surfaceVariant: T,
|
||||||
)
|
)
|
||||||
|
|
||||||
typealias FullColorScheme = ColorScheme<Color>
|
typealias FullColorScheme = ColorScheme<Color>
|
||||||
|
|||||||
@ -6,8 +6,25 @@ import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
|||||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||||
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.polymorphic
|
||||||
|
|
||||||
internal class ColorSerializer: KSerializer<Color> {
|
internal val module = SerializersModule {
|
||||||
|
contextual(Color::class, ColorSerializer)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
val ThemeJson = Json {
|
||||||
|
serializersModule = module
|
||||||
|
encodeDefaults = true
|
||||||
|
ignoreUnknownKeys = true
|
||||||
|
explicitNulls = false
|
||||||
|
isLenient = true
|
||||||
|
coerceInputValues = true
|
||||||
|
}
|
||||||
|
|
||||||
|
internal object ColorSerializer: KSerializer<Color> {
|
||||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ColorSerializer", PrimitiveKind.STRING)
|
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ColorSerializer", PrimitiveKind.STRING)
|
||||||
|
|
||||||
override fun serialize(
|
override fun serialize(
|
||||||
@ -18,12 +35,12 @@ internal class ColorSerializer: KSerializer<Color> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun deserialize(decoder: Decoder): Color {
|
override fun deserialize(decoder: Decoder): Color {
|
||||||
TODO("Not yet implemented")
|
val stringValue = decoder.decodeString()
|
||||||
|
return Color.fromString(stringValue) ?: StaticColor(0xFF000000.toInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class ShapeSerializer: KSerializer<Shape> {
|
internal object ShapeSerializer: KSerializer<Shape> {
|
||||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ShapeSerializer", PrimitiveKind.STRING)
|
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ShapeSerializer", PrimitiveKind.STRING)
|
||||||
|
|
||||||
override fun serialize(
|
override fun serialize(
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
package de.mm20.launcher2.themes
|
package de.mm20.launcher2.themes
|
||||||
|
|
||||||
import de.mm20.launcher2.database.entities.ShapesEntity
|
import de.mm20.launcher2.database.entities.ShapesEntity
|
||||||
|
import de.mm20.launcher2.serialization.UUIDSerializer
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.Transient
|
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Shapes(
|
data class Shapes(
|
||||||
@Transient val id: UUID = UUID.randomUUID(),
|
@Serializable(with = UUIDSerializer::class) val id: UUID = UUID.randomUUID(),
|
||||||
val builtIn: Boolean = false,
|
val builtIn: Boolean = false,
|
||||||
val name: String,
|
val name: String,
|
||||||
val baseShape: Shape = Shape(
|
val baseShape: Shape = Shape(
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package de.mm20.launcher2.themes
|
package de.mm20.launcher2.themes
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import de.mm20.launcher2.crashreporter.CrashReporter
|
import de.mm20.launcher2.crashreporter.CrashReporter
|
||||||
import de.mm20.launcher2.serialization.Json
|
import de.mm20.launcher2.serialization.Json
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@ -7,10 +8,13 @@ import kotlinx.coroutines.withContext
|
|||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.SerializationException
|
import kotlinx.serialization.SerializationException
|
||||||
import kotlinx.serialization.json.JsonElement
|
import kotlinx.serialization.json.JsonElement
|
||||||
|
import kotlinx.serialization.json.JsonObject
|
||||||
import kotlinx.serialization.json.JsonPrimitive
|
import kotlinx.serialization.json.JsonPrimitive
|
||||||
|
import kotlinx.serialization.json.contentOrNull
|
||||||
import kotlinx.serialization.json.decodeFromJsonElement
|
import kotlinx.serialization.json.decodeFromJsonElement
|
||||||
import kotlinx.serialization.json.intOrNull
|
import kotlinx.serialization.json.intOrNull
|
||||||
import kotlinx.serialization.json.jsonObject
|
import kotlinx.serialization.json.jsonObject
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ThemeBundle(
|
data class ThemeBundle(
|
||||||
@ -24,21 +28,23 @@ data class ThemeBundle(
|
|||||||
val version: Int = 2,
|
val version: Int = 2,
|
||||||
) {
|
) {
|
||||||
fun toJson(): String {
|
fun toJson(): String {
|
||||||
return Json.Lenient.encodeToString(this)
|
return ThemeJson.encodeToString(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun fromJson(jsonString: String): ThemeBundle? {
|
fun fromJson(jsonString: String): ThemeBundle? {
|
||||||
try {
|
try {
|
||||||
val jsonElement = Json.Lenient.parseToJsonElement(jsonString).jsonObject
|
val jsonElement = ThemeJson.parseToJsonElement(jsonString).jsonObject
|
||||||
|
|
||||||
val version = (jsonElement.get("version") as? JsonPrimitive)?.intOrNull
|
val version = (jsonElement["version"] as? JsonPrimitive)?.intOrNull
|
||||||
|
|
||||||
if (version != 2) {
|
if (version != 2) {
|
||||||
return fromLegacyJson(jsonElement)
|
return fromLegacyJson(jsonElement)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Json.Lenient.decodeFromJsonElement(jsonElement)
|
return ThemeJson.decodeFromJsonElement<ThemeBundle>(jsonElement).also {
|
||||||
|
Log.d("MM20", "$it")
|
||||||
|
}
|
||||||
|
|
||||||
} catch (e: SerializationException) {
|
} catch (e: SerializationException) {
|
||||||
CrashReporter.logException(e)
|
CrashReporter.logException(e)
|
||||||
@ -49,9 +55,26 @@ data class ThemeBundle(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fromLegacyJson(jsonElement: JsonElement): ThemeBundle? {
|
private fun fromLegacyJson(jsonElement: JsonObject): ThemeBundle? {
|
||||||
try {
|
try {
|
||||||
val colorScheme: Colors = LegacyThemeJson.decodeFromJsonElement(jsonElement)
|
val name = (jsonElement["name"] as? JsonPrimitive)?.contentOrNull ?: return null
|
||||||
|
val corePalette = (jsonElement["corePalette"] as? JsonObject)?.let {
|
||||||
|
LegacyThemeJson.decodeFromJsonElement<CorePalette<Int?>>(it)
|
||||||
|
}
|
||||||
|
val lightColorScheme = (jsonElement["lightColorScheme"] as? JsonObject)?.let {
|
||||||
|
LegacyThemeJson.decodeFromJsonElement<ColorScheme<Color?>>(it)
|
||||||
|
}
|
||||||
|
val darkColorScheme = (jsonElement["darkColorScheme"] as? JsonObject)?.let {
|
||||||
|
LegacyThemeJson.decodeFromJsonElement<ColorScheme<Color?>>(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
val colorScheme = Colors(
|
||||||
|
id = UUID.randomUUID(),
|
||||||
|
name = name,
|
||||||
|
corePalette = corePalette ?: EmptyCorePalette,
|
||||||
|
lightColorScheme = lightColorScheme ?: DefaultLightColorScheme,
|
||||||
|
darkColorScheme = darkColorScheme ?: DefaultDarkColorScheme,
|
||||||
|
)
|
||||||
return ThemeBundle(
|
return ThemeBundle(
|
||||||
name = colorScheme.name,
|
name = colorScheme.name,
|
||||||
author = "",
|
author = "",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user