parent
28232a33b4
commit
5ac4022eb5
@ -37,6 +37,7 @@ import de.mm20.launcher2.ui.locals.LocalWallpaperColors
|
|||||||
import de.mm20.launcher2.ui.overlays.OverlayHost
|
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.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
|
||||||
@ -159,6 +160,9 @@ class SettingsActivity : BaseActivity() {
|
|||||||
composable("settings/appearance") {
|
composable("settings/appearance") {
|
||||||
AppearanceSettingsScreen()
|
AppearanceSettingsScreen()
|
||||||
}
|
}
|
||||||
|
composable("settings/appearance/export") {
|
||||||
|
ExportThemeSettingsScreen()
|
||||||
|
}
|
||||||
composable("settings/homescreen") {
|
composable("settings/homescreen") {
|
||||||
HomescreenSettingsScreen()
|
HomescreenSettingsScreen()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
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.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.ArrowCircleDown
|
||||||
|
import androidx.compose.material.icons.rounded.ArrowCircleUp
|
||||||
import androidx.compose.material.icons.rounded.CropSquare
|
import androidx.compose.material.icons.rounded.CropSquare
|
||||||
import androidx.compose.material.icons.rounded.Palette
|
import androidx.compose.material.icons.rounded.Palette
|
||||||
import androidx.compose.material.icons.rounded.TextFields
|
import androidx.compose.material.icons.rounded.TextFields
|
||||||
@ -100,6 +102,22 @@ fun AppearanceSettingsScreen() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
PreferenceCategory {
|
||||||
|
Preference(
|
||||||
|
title = "Import",
|
||||||
|
icon = Icons.Rounded.ArrowCircleDown,
|
||||||
|
)
|
||||||
|
Preference(
|
||||||
|
title = "Export",
|
||||||
|
icon = Icons.Rounded.ArrowCircleUp,
|
||||||
|
onClick = {
|
||||||
|
navController?.navigate("settings/appearance/export")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (isAtLeastApiLevel(31)) {
|
if (isAtLeastApiLevel(31)) {
|
||||||
item {
|
item {
|
||||||
PreferenceCategory(stringResource(R.string.preference_category_advanced)) {
|
PreferenceCategory(stringResource(R.string.preference_category_advanced)) {
|
||||||
|
|||||||
@ -0,0 +1,181 @@
|
|||||||
|
package de.mm20.launcher2.ui.settings.appearance
|
||||||
|
|
||||||
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
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.CropSquare
|
||||||
|
import androidx.compose.material.icons.rounded.KeyboardArrowDown
|
||||||
|
import androidx.compose.material.icons.rounded.Palette
|
||||||
|
import androidx.compose.material.icons.rounded.Save
|
||||||
|
import androidx.compose.material.icons.rounded.Share
|
||||||
|
import androidx.compose.material3.ButtonDefaults
|
||||||
|
import androidx.compose.material3.DropdownMenu
|
||||||
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.SplitButtonDefaults
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import de.mm20.launcher2.ui.R
|
||||||
|
import de.mm20.launcher2.ui.component.preferences.ListPreference
|
||||||
|
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
|
||||||
|
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
|
||||||
|
import de.mm20.launcher2.ui.component.preferences.TextPreference
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ExportThemeSettingsScreen() {
|
||||||
|
val viewModel = viewModel<ExportThemeSettingsScreenVM>()
|
||||||
|
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
val colorSchemes by viewModel.colorSchemes.collectAsState(emptyList())
|
||||||
|
val shapeThemes by viewModel.shapeSchemes.collectAsState(emptyList())
|
||||||
|
|
||||||
|
val isValidSelection by remember {
|
||||||
|
derivedStateOf {
|
||||||
|
viewModel.colorScheme != null || viewModel.shapeScheme != null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val fileChooserLauncher = rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument("application/vnd.de.mm20.launcher2.theme")) {
|
||||||
|
if (it != null) viewModel.exportTheme(context, it)
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
viewModel.init()
|
||||||
|
}
|
||||||
|
PreferenceScreen(
|
||||||
|
title = stringResource(R.string.theme_export_title)
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
PreferenceCategory {
|
||||||
|
TextPreference(
|
||||||
|
stringResource(R.string.theme_bundle_name),
|
||||||
|
value = viewModel.themeName,
|
||||||
|
summary = viewModel.themeName.takeIf { it.isNotBlank() },
|
||||||
|
onValueChanged = {
|
||||||
|
viewModel.themeName = it
|
||||||
|
}
|
||||||
|
)
|
||||||
|
TextPreference(
|
||||||
|
stringResource(R.string.theme_bundle_author),
|
||||||
|
value = viewModel.themeAuthor,
|
||||||
|
summary = viewModel.themeAuthor.takeIf { it.isNotBlank() },
|
||||||
|
onValueChanged = {
|
||||||
|
viewModel.themeAuthor = it
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
PreferenceCategory {
|
||||||
|
ListPreference(
|
||||||
|
stringResource(R.string.preference_screen_colors),
|
||||||
|
icon = Icons.Rounded.Palette,
|
||||||
|
value = viewModel.colorScheme,
|
||||||
|
items = listOf(stringResource(R.string.no_selection) to null) + colorSchemes.map {
|
||||||
|
it.name to it
|
||||||
|
},
|
||||||
|
onValueChanged = { newValue ->
|
||||||
|
viewModel.setColorScheme(newValue)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
ListPreference(
|
||||||
|
stringResource(R.string.preference_screen_shapes),
|
||||||
|
icon = Icons.Rounded.CropSquare,
|
||||||
|
value = viewModel.shapeScheme,
|
||||||
|
items = listOf(stringResource(R.string.no_selection) to null) + shapeThemes.map {
|
||||||
|
it.name to it
|
||||||
|
},
|
||||||
|
onValueChanged = { newValue ->
|
||||||
|
viewModel.setShapeScheme(newValue)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
PreferenceCategory {
|
||||||
|
var showDropdown by remember { mutableStateOf(false) }
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(
|
||||||
|
MaterialTheme.colorScheme.surface,
|
||||||
|
MaterialTheme.shapes.extraSmall
|
||||||
|
)
|
||||||
|
.padding(12.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(SplitButtonDefaults.Spacing)
|
||||||
|
) {
|
||||||
|
SplitButtonDefaults.LeadingButton(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
enabled = isValidSelection,
|
||||||
|
onClick = {
|
||||||
|
viewModel.shareTheme(context)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Rounded.Share,
|
||||||
|
null,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(end = ButtonDefaults.IconSpacing)
|
||||||
|
.size(SplitButtonDefaults.LeadingIconSize),
|
||||||
|
)
|
||||||
|
Text(stringResource(R.string.menu_share))
|
||||||
|
}
|
||||||
|
SplitButtonDefaults.TrailingButton(
|
||||||
|
onClick = {
|
||||||
|
showDropdown = !showDropdown
|
||||||
|
},
|
||||||
|
enabled = isValidSelection,
|
||||||
|
) {
|
||||||
|
val rotation: Float by animateFloatAsState(
|
||||||
|
targetValue = if (showDropdown) 180f else 0f,
|
||||||
|
label = "Trailing Icon Rotation"
|
||||||
|
)
|
||||||
|
Icon(
|
||||||
|
Icons.Rounded.KeyboardArrowDown,
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.size(SplitButtonDefaults.TrailingIconSize)
|
||||||
|
.graphicsLayer {
|
||||||
|
this.rotationZ = rotation
|
||||||
|
},
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
|
||||||
|
DropdownMenu(expanded = showDropdown, onDismissRequest = { showDropdown = false }) {
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(stringResource(R.string.save_as_file)) },
|
||||||
|
onClick = {
|
||||||
|
fileChooserLauncher.launch("${viewModel.themeName}.kvtheme")
|
||||||
|
showDropdown = false
|
||||||
|
},
|
||||||
|
leadingIcon = { Icon(Icons.Rounded.Save, contentDescription = null) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,95 @@
|
|||||||
|
package de.mm20.launcher2.ui.settings.appearance
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.core.content.FileProvider
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import de.mm20.launcher2.ktx.tryStartActivity
|
||||||
|
import de.mm20.launcher2.themes.Colors
|
||||||
|
import de.mm20.launcher2.themes.Shapes
|
||||||
|
import de.mm20.launcher2.themes.ThemeBundle
|
||||||
|
import de.mm20.launcher2.themes.ThemeRepository
|
||||||
|
import de.mm20.launcher2.themes.toLegacyJson
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.koin.core.component.KoinComponent
|
||||||
|
import org.koin.core.component.inject
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class ExportThemeSettingsScreenVM: ViewModel(), KoinComponent {
|
||||||
|
|
||||||
|
private val themeRepository: ThemeRepository by inject()
|
||||||
|
|
||||||
|
val colorSchemes = themeRepository.getAllColors().map { it.filter { !it.builtIn } }
|
||||||
|
val shapeSchemes = themeRepository.getAllShapes().map { it.filter { !it.builtIn } }
|
||||||
|
|
||||||
|
var themeName by mutableStateOf("")
|
||||||
|
var themeAuthor by mutableStateOf("")
|
||||||
|
|
||||||
|
|
||||||
|
fun init() {
|
||||||
|
themeName = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var colorScheme by mutableStateOf<Colors?>(null)
|
||||||
|
@JvmName("_setColorScheme")
|
||||||
|
private set
|
||||||
|
fun setColorScheme(scheme: Colors?) {
|
||||||
|
if (themeName.isBlank() && scheme != null) themeName = scheme.name
|
||||||
|
colorScheme = scheme
|
||||||
|
}
|
||||||
|
|
||||||
|
var shapeScheme by mutableStateOf<Shapes?>(null)
|
||||||
|
@JvmName("_setShapeScheme")
|
||||||
|
private set
|
||||||
|
fun setShapeScheme(scheme: Shapes?) {
|
||||||
|
if (themeName.isBlank() && scheme != null) themeName = scheme.name
|
||||||
|
shapeScheme = scheme
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getThemeBundle(): ThemeBundle {
|
||||||
|
return ThemeBundle(
|
||||||
|
name = themeName,
|
||||||
|
author = themeAuthor.takeIf { it.isNotBlank() },
|
||||||
|
colors = colorScheme,
|
||||||
|
shapes = shapeScheme,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun exportTheme(context: Context, uri: Uri) {
|
||||||
|
val themeBundle = getThemeBundle()
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
context.contentResolver.openOutputStream(uri)?.writer()?.use {
|
||||||
|
it.write(themeBundle.toJson())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun shareTheme(context: Context) {
|
||||||
|
val themeBundle = getThemeBundle()
|
||||||
|
viewModelScope.launch {
|
||||||
|
val file = withContext(Dispatchers.IO) {
|
||||||
|
val file = File(context.cacheDir, "${themeName}.kvtheme")
|
||||||
|
file.writeText(themeBundle.toJson())
|
||||||
|
file
|
||||||
|
}
|
||||||
|
context.tryStartActivity(Intent().apply {
|
||||||
|
action = Intent.ACTION_SEND
|
||||||
|
type = "application/json"
|
||||||
|
putExtra(Intent.EXTRA_STREAM, FileProvider.getUriForFile(
|
||||||
|
context,
|
||||||
|
context.applicationContext.packageName + ".fileprovider",
|
||||||
|
file
|
||||||
|
))
|
||||||
|
}.let { Intent.createChooser(it, null) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -159,7 +159,6 @@ fun ColorSchemeSettingsScreen(themeId: UUID) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
defaultValue = systemPalette.primary,
|
defaultValue = systemPalette.primary,
|
||||||
modifier = Modifier.padding(end = 12.dp),
|
|
||||||
)
|
)
|
||||||
CorePaletteColorPreference(
|
CorePaletteColorPreference(
|
||||||
title = "Secondary",
|
title = "Secondary",
|
||||||
@ -174,7 +173,6 @@ fun ColorSchemeSettingsScreen(themeId: UUID) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
defaultValue = systemPalette.secondary,
|
defaultValue = systemPalette.secondary,
|
||||||
modifier = Modifier.padding(end = 12.dp),
|
|
||||||
autoGenerate = {
|
autoGenerate = {
|
||||||
theme!!.corePalette.primary?.let {
|
theme!!.corePalette.primary?.let {
|
||||||
CorePalette.of(it).a2.keyColor.toInt()
|
CorePalette.of(it).a2.keyColor.toInt()
|
||||||
@ -194,7 +192,6 @@ fun ColorSchemeSettingsScreen(themeId: UUID) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
defaultValue = systemPalette.tertiary,
|
defaultValue = systemPalette.tertiary,
|
||||||
modifier = Modifier.padding(end = 12.dp),
|
|
||||||
autoGenerate = {
|
autoGenerate = {
|
||||||
theme!!.corePalette.primary?.let {
|
theme!!.corePalette.primary?.let {
|
||||||
CorePalette.of(it).a3.keyColor.toInt()
|
CorePalette.of(it).a3.keyColor.toInt()
|
||||||
@ -214,7 +211,6 @@ fun ColorSchemeSettingsScreen(themeId: UUID) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
defaultValue = systemPalette.neutral,
|
defaultValue = systemPalette.neutral,
|
||||||
modifier = Modifier.padding(end = 12.dp),
|
|
||||||
autoGenerate = {
|
autoGenerate = {
|
||||||
theme!!.corePalette.primary?.let {
|
theme!!.corePalette.primary?.let {
|
||||||
CorePalette.of(it).n1.keyColor.toInt()
|
CorePalette.of(it).n1.keyColor.toInt()
|
||||||
@ -234,7 +230,6 @@ fun ColorSchemeSettingsScreen(themeId: UUID) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
defaultValue = systemPalette.neutralVariant,
|
defaultValue = systemPalette.neutralVariant,
|
||||||
modifier = Modifier.padding(end = 12.dp),
|
|
||||||
autoGenerate = {
|
autoGenerate = {
|
||||||
theme!!.corePalette.primary?.let {
|
theme!!.corePalette.primary?.let {
|
||||||
CorePalette.of(it).n2.keyColor.toInt()
|
CorePalette.of(it).n2.keyColor.toInt()
|
||||||
|
|||||||
@ -67,24 +67,13 @@ fun ColorSchemesSettingsScreen() {
|
|||||||
|
|
||||||
var deleteColors by remember { mutableStateOf<Colors?>(null) }
|
var deleteColors by remember { mutableStateOf<Colors?>(null) }
|
||||||
|
|
||||||
var importThemeUri by remember { mutableStateOf<Uri?>(null) }
|
|
||||||
|
|
||||||
val importIntentLauncher = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) {
|
|
||||||
importThemeUri = it
|
|
||||||
}
|
|
||||||
|
|
||||||
PreferenceScreen(
|
PreferenceScreen(
|
||||||
title = stringResource(R.string.preference_screen_colors),
|
title = stringResource(R.string.preference_screen_colors),
|
||||||
topBarActions = {
|
topBarActions = {
|
||||||
IconButton(onClick = { importIntentLauncher.launch(arrayOf("*/*")) }) {
|
IconButton(onClick = { viewModel.createNew(context) }) {
|
||||||
Icon(Icons.Rounded.Download, null)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
floatingActionButton = {
|
|
||||||
FloatingActionButton(onClick = { viewModel.createNew(context) }) {
|
|
||||||
Icon(Icons.Rounded.Add, null)
|
Icon(Icons.Rounded.Add, null)
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
) {
|
) {
|
||||||
item {
|
item {
|
||||||
PreferenceCategory {
|
PreferenceCategory {
|
||||||
@ -192,10 +181,6 @@ fun ColorSchemesSettingsScreen() {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (importThemeUri != null) {
|
|
||||||
ImportThemeSheet(uri = importThemeUri!!, onDismiss = { importThemeUri = null })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@ -18,7 +18,6 @@ import androidx.compose.material.icons.rounded.RadioButtonUnchecked
|
|||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.DropdownMenu
|
import androidx.compose.material3.DropdownMenu
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
import androidx.compose.material3.FloatingActionButton
|
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@ -43,7 +42,6 @@ import de.mm20.launcher2.ui.component.preferences.Preference
|
|||||||
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
|
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
|
||||||
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
|
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
|
||||||
import de.mm20.launcher2.ui.locals.LocalNavController
|
import de.mm20.launcher2.ui.locals.LocalNavController
|
||||||
import de.mm20.launcher2.ui.theme.shapes.shapesOf
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ShapeSchemesSettingsScreen() {
|
fun ShapeSchemesSettingsScreen() {
|
||||||
@ -58,11 +56,11 @@ fun ShapeSchemesSettingsScreen() {
|
|||||||
|
|
||||||
PreferenceScreen(
|
PreferenceScreen(
|
||||||
title = stringResource(R.string.preference_screen_shapes),
|
title = stringResource(R.string.preference_screen_shapes),
|
||||||
floatingActionButton = {
|
topBarActions = {
|
||||||
FloatingActionButton(onClick = { viewModel.createNew(context) }) {
|
IconButton(onClick = { viewModel.createNew(context) }) {
|
||||||
Icon(Icons.Rounded.Add, null)
|
Icon(Icons.Rounded.Add, null)
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
) {
|
) {
|
||||||
item {
|
item {
|
||||||
PreferenceCategory {
|
PreferenceCategory {
|
||||||
|
|||||||
@ -243,7 +243,9 @@
|
|||||||
<string name="easter_egg_text">Well, you found me. Congratulations. Was it worth it\?</string>
|
<string name="easter_egg_text">Well, you found me. Congratulations. Was it worth it\?</string>
|
||||||
<!-- Close a dialog -->
|
<!-- Close a dialog -->
|
||||||
<string name="close">Close</string>
|
<string name="close">Close</string>
|
||||||
|
<string name="no_selection">No selection</string>
|
||||||
<string name="save">Save</string>
|
<string name="save">Save</string>
|
||||||
|
<string name="save_as_file">Save as file</string>
|
||||||
<string name="duplicate">Duplicate</string>
|
<string name="duplicate">Duplicate</string>
|
||||||
<string name="edit">Edit</string>
|
<string name="edit">Edit</string>
|
||||||
<string name="skip">Skip</string>
|
<string name="skip">Skip</string>
|
||||||
@ -1029,4 +1031,7 @@
|
|||||||
<string name="departure_time_departed">departed</string>
|
<string name="departure_time_departed">departed</string>
|
||||||
<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_bundle_name">Name</string>
|
||||||
|
<string name="theme_bundle_author">Author</string>
|
||||||
</resources>
|
</resources>
|
||||||
@ -0,0 +1,69 @@
|
|||||||
|
package de.mm20.launcher2.themes
|
||||||
|
|
||||||
|
import de.mm20.launcher2.crashreporter.CrashReporter
|
||||||
|
import de.mm20.launcher2.serialization.Json
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.SerializationException
|
||||||
|
import kotlinx.serialization.json.JsonElement
|
||||||
|
import kotlinx.serialization.json.JsonPrimitive
|
||||||
|
import kotlinx.serialization.json.decodeFromJsonElement
|
||||||
|
import kotlinx.serialization.json.intOrNull
|
||||||
|
import kotlinx.serialization.json.jsonObject
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ThemeBundle(
|
||||||
|
val name: String,
|
||||||
|
val author: String? = null,
|
||||||
|
val colors: Colors? = null,
|
||||||
|
val shapes: Shapes? = null,
|
||||||
|
/**
|
||||||
|
* The file version, always 2 for the new theme format.
|
||||||
|
*/
|
||||||
|
val version: Int = 2,
|
||||||
|
) {
|
||||||
|
fun toJson(): String {
|
||||||
|
return Json.Lenient.encodeToString(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromJson(jsonString: String): ThemeBundle? {
|
||||||
|
try {
|
||||||
|
val jsonElement = Json.Lenient.parseToJsonElement(jsonString).jsonObject
|
||||||
|
|
||||||
|
val version = (jsonElement.get("version") as? JsonPrimitive)?.intOrNull
|
||||||
|
|
||||||
|
if (version != 2) {
|
||||||
|
return fromLegacyJson(jsonElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Json.Lenient.decodeFromJsonElement(jsonElement)
|
||||||
|
|
||||||
|
} catch (e: SerializationException) {
|
||||||
|
CrashReporter.logException(e)
|
||||||
|
return null
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
CrashReporter.logException(e)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fromLegacyJson(jsonElement: JsonElement): ThemeBundle? {
|
||||||
|
try {
|
||||||
|
val colorScheme: Colors = LegacyThemeJson.decodeFromJsonElement(jsonElement)
|
||||||
|
return ThemeBundle(
|
||||||
|
name = colorScheme.name,
|
||||||
|
author = "",
|
||||||
|
colors = colorScheme,
|
||||||
|
shapes = null,
|
||||||
|
version = 2,
|
||||||
|
)
|
||||||
|
} catch (e: SerializationException) {
|
||||||
|
CrashReporter.logException(e)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user