Add intent to import theme files directly
This commit is contained in:
parent
e516848f4f
commit
e83a6e8a31
@ -25,7 +25,8 @@
|
|||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
<meta-data android:name="android.app.shortcuts"
|
<meta-data
|
||||||
|
android:name="android.app.shortcuts"
|
||||||
android:resource="@xml/shortcuts" />
|
android:resource="@xml/shortcuts" />
|
||||||
|
|
||||||
</activity>
|
</activity>
|
||||||
@ -62,9 +63,27 @@
|
|||||||
android:value="de.mm20.launcher2.ui.launcher.SharedLauncherActivity" />
|
android:value="de.mm20.launcher2.ui.launcher.SharedLauncherActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name=".launcher.sheets.BindAndConfigureAppWidgetActivity" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".launcher.sheets.BindAndConfigureAppWidgetActivity"
|
android:name=".launcher.ImportThemeActivity"
|
||||||
/>
|
android:excludeFromRecents="true"
|
||||||
|
android:exported="true"
|
||||||
|
android:theme="@style/DialogTheme">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:mimeType="application/vnd.de.mm20.launcher2.theme" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data
|
||||||
|
android:host="*"
|
||||||
|
android:mimeType="*/*"
|
||||||
|
android:pathSuffix="kvtheme" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
@ -0,0 +1,310 @@
|
|||||||
|
package de.mm20.launcher2.ui.common
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.compose.foundation.BorderStroke
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.RowScope
|
||||||
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.DarkMode
|
||||||
|
import androidx.compose.material.icons.rounded.ErrorOutline
|
||||||
|
import androidx.compose.material.icons.rounded.LightMode
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.SegmentedButton
|
||||||
|
import androidx.compose.material3.SegmentedButtonDefaults
|
||||||
|
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
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.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.toArgb
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import de.mm20.launcher2.themes.Theme
|
||||||
|
import de.mm20.launcher2.ui.R
|
||||||
|
import de.mm20.launcher2.ui.component.BottomSheetDialog
|
||||||
|
import de.mm20.launcher2.ui.component.LargeMessage
|
||||||
|
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 hct.Hct
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ImportThemeSheet(
|
||||||
|
uri: Uri,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
) {
|
||||||
|
val viewModel: ImportThemeSheetVM = viewModel()
|
||||||
|
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
LaunchedEffect(uri) {
|
||||||
|
viewModel.readTheme(context, uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
val theme by viewModel.theme
|
||||||
|
val error by viewModel.error
|
||||||
|
var apply by viewModel.apply
|
||||||
|
|
||||||
|
BottomSheetDialog(
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
confirmButton = if (theme != null && !error) {
|
||||||
|
{
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
viewModel.import()
|
||||||
|
onDismiss()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.action_import))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else null,
|
||||||
|
) {
|
||||||
|
if (theme == null && !error) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(it)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.aspectRatio(1f),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
CircularProgressIndicator()
|
||||||
|
}
|
||||||
|
} else if (error) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(it)
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
LargeMessage(
|
||||||
|
icon = Icons.Rounded.ErrorOutline,
|
||||||
|
text = "Theme could not be read. Is the file corrupted?"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
.padding(it)
|
||||||
|
) {
|
||||||
|
ThemePreview(
|
||||||
|
theme!!,
|
||||||
|
)
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.padding(top = 8.dp),
|
||||||
|
color = MaterialTheme.colorScheme.surfaceContainer,
|
||||||
|
shape = MaterialTheme.shapes.medium,
|
||||||
|
border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant)
|
||||||
|
) {
|
||||||
|
|
||||||
|
SwitchPreference(
|
||||||
|
title = stringResource(R.string.import_theme_apply),
|
||||||
|
iconPadding = false,
|
||||||
|
value = apply,
|
||||||
|
onValueChanged = {
|
||||||
|
apply = it
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ThemePreview(
|
||||||
|
theme: Theme,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
val darkMode = LocalDarkTheme.current
|
||||||
|
var darkTheme by remember { mutableStateOf(darkMode) }
|
||||||
|
|
||||||
|
val colorScheme = if (darkTheme) darkColorSchemeOf(theme) else lightColorSchemeOf(theme)
|
||||||
|
|
||||||
|
Column(modifier = modifier) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(top = 8.dp, bottom = 16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = theme.name,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
SingleChoiceSegmentedButtonRow {
|
||||||
|
SegmentedButton(
|
||||||
|
shape = SegmentedButtonDefaults.shape(position = 0, count = 2),
|
||||||
|
selected = !darkTheme,
|
||||||
|
onClick = { darkTheme = false }
|
||||||
|
) {
|
||||||
|
Icon(Icons.Rounded.LightMode, null)
|
||||||
|
}
|
||||||
|
SegmentedButton(
|
||||||
|
shape = SegmentedButtonDefaults.shape(position = 1, count = 2),
|
||||||
|
selected = darkTheme,
|
||||||
|
onClick = { darkTheme = true }
|
||||||
|
) {
|
||||||
|
Icon(Icons.Rounded.DarkMode, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MaterialTheme(
|
||||||
|
colorScheme = colorScheme
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(MaterialTheme.shapes.medium)
|
||||||
|
.border(1.dp, MaterialTheme.colorScheme.outlineVariant, MaterialTheme.shapes.medium)
|
||||||
|
.background(MaterialTheme.colorScheme.surfaceContainer)
|
||||||
|
.padding(start = 14.dp, end = 14.dp, bottom = 14.dp, top = 14.dp)
|
||||||
|
) {
|
||||||
|
Row {
|
||||||
|
ColorSwatch(color = MaterialTheme.colorScheme.primary, darkTheme = darkTheme)
|
||||||
|
ColorSwatch(color = MaterialTheme.colorScheme.onPrimary, darkTheme = darkTheme)
|
||||||
|
ColorSwatch(
|
||||||
|
color = MaterialTheme.colorScheme.primaryContainer,
|
||||||
|
darkTheme = darkTheme
|
||||||
|
)
|
||||||
|
ColorSwatch(
|
||||||
|
color = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||||
|
darkTheme = darkTheme
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Row {
|
||||||
|
ColorSwatch(color = MaterialTheme.colorScheme.secondary, darkTheme = darkTheme)
|
||||||
|
ColorSwatch(color = MaterialTheme.colorScheme.onSecondary, darkTheme = darkTheme)
|
||||||
|
ColorSwatch(
|
||||||
|
color = MaterialTheme.colorScheme.secondaryContainer,
|
||||||
|
darkTheme = darkTheme
|
||||||
|
)
|
||||||
|
ColorSwatch(
|
||||||
|
color = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||||
|
darkTheme = darkTheme
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Row {
|
||||||
|
ColorSwatch(color = MaterialTheme.colorScheme.tertiary, darkTheme = darkTheme)
|
||||||
|
ColorSwatch(color = MaterialTheme.colorScheme.onTertiary, darkTheme = darkTheme)
|
||||||
|
ColorSwatch(
|
||||||
|
color = MaterialTheme.colorScheme.tertiaryContainer,
|
||||||
|
darkTheme = darkTheme
|
||||||
|
)
|
||||||
|
ColorSwatch(
|
||||||
|
color = MaterialTheme.colorScheme.onTertiaryContainer,
|
||||||
|
darkTheme = darkTheme
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Row {
|
||||||
|
ColorSwatch(color = MaterialTheme.colorScheme.error, darkTheme = darkTheme)
|
||||||
|
ColorSwatch(color = MaterialTheme.colorScheme.onError, darkTheme = darkTheme)
|
||||||
|
ColorSwatch(color = MaterialTheme.colorScheme.errorContainer, darkTheme = darkTheme)
|
||||||
|
ColorSwatch(
|
||||||
|
color = MaterialTheme.colorScheme.onErrorContainer,
|
||||||
|
darkTheme = darkTheme
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Row {
|
||||||
|
ColorSwatch(color = MaterialTheme.colorScheme.surfaceDim, darkTheme = darkTheme)
|
||||||
|
ColorSwatch(color = MaterialTheme.colorScheme.surface, darkTheme = darkTheme)
|
||||||
|
ColorSwatch(color = MaterialTheme.colorScheme.surfaceBright, darkTheme = darkTheme)
|
||||||
|
}
|
||||||
|
Row {
|
||||||
|
ColorSwatch(
|
||||||
|
color = MaterialTheme.colorScheme.surfaceContainerLowest,
|
||||||
|
darkTheme = darkTheme
|
||||||
|
)
|
||||||
|
ColorSwatch(
|
||||||
|
color = MaterialTheme.colorScheme.surfaceContainerLow,
|
||||||
|
darkTheme = darkTheme
|
||||||
|
)
|
||||||
|
ColorSwatch(
|
||||||
|
color = MaterialTheme.colorScheme.surfaceContainer,
|
||||||
|
darkTheme = darkTheme
|
||||||
|
)
|
||||||
|
ColorSwatch(
|
||||||
|
color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||||
|
darkTheme = darkTheme
|
||||||
|
)
|
||||||
|
ColorSwatch(
|
||||||
|
color = MaterialTheme.colorScheme.surfaceContainerHighest,
|
||||||
|
darkTheme = darkTheme
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Row {
|
||||||
|
ColorSwatch(color = MaterialTheme.colorScheme.onSurface, darkTheme = darkTheme)
|
||||||
|
ColorSwatch(
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
darkTheme = darkTheme
|
||||||
|
)
|
||||||
|
ColorSwatch(color = MaterialTheme.colorScheme.outline, darkTheme = darkTheme)
|
||||||
|
ColorSwatch(color = MaterialTheme.colorScheme.outlineVariant, darkTheme = darkTheme)
|
||||||
|
}
|
||||||
|
Row {
|
||||||
|
ColorSwatch(
|
||||||
|
color = MaterialTheme.colorScheme.inverseSurface,
|
||||||
|
darkTheme = darkTheme,
|
||||||
|
weight = 2f
|
||||||
|
)
|
||||||
|
ColorSwatch(
|
||||||
|
color = MaterialTheme.colorScheme.inverseOnSurface,
|
||||||
|
darkTheme = darkTheme
|
||||||
|
)
|
||||||
|
ColorSwatch(color = MaterialTheme.colorScheme.inversePrimary, darkTheme = darkTheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RowScope.ColorSwatch(
|
||||||
|
color: Color,
|
||||||
|
darkTheme: Boolean,
|
||||||
|
weight: Float = 1f,
|
||||||
|
) {
|
||||||
|
val borderColor = Color(Hct.fromInt(color.toArgb()).let {
|
||||||
|
val tone = if (darkTheme) 30f else 80f
|
||||||
|
it.apply {
|
||||||
|
this.tone = tone.toDouble()
|
||||||
|
}.toInt()
|
||||||
|
})
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(weight)
|
||||||
|
.padding(2.dp)
|
||||||
|
.height(36.dp)
|
||||||
|
.clip(MaterialTheme.shapes.small)
|
||||||
|
.border(
|
||||||
|
1.dp,
|
||||||
|
borderColor,
|
||||||
|
MaterialTheme.shapes.small
|
||||||
|
)
|
||||||
|
.background(color),
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,71 @@
|
|||||||
|
package de.mm20.launcher2.ui.common
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||||
|
import de.mm20.launcher2.themes.Theme
|
||||||
|
import de.mm20.launcher2.themes.ThemeRepository
|
||||||
|
import de.mm20.launcher2.themes.fromJson
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.koin.core.component.KoinComponent
|
||||||
|
import org.koin.core.component.inject
|
||||||
|
|
||||||
|
class ImportThemeSheetVM : ViewModel(), KoinComponent {
|
||||||
|
|
||||||
|
private val themeRepository: ThemeRepository by inject()
|
||||||
|
private val dataStore: LauncherDataStore by inject()
|
||||||
|
|
||||||
|
val theme = mutableStateOf<Theme?>(null)
|
||||||
|
val error = mutableStateOf<Boolean>(false)
|
||||||
|
val apply = mutableStateOf<Boolean>(false)
|
||||||
|
|
||||||
|
fun import() {
|
||||||
|
val theme = theme.value
|
||||||
|
val apply = apply.value
|
||||||
|
if (theme != null) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
importTheme(theme, apply)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun readTheme(context: Context, uri: Uri) {
|
||||||
|
error.value = false
|
||||||
|
theme.value = null
|
||||||
|
apply.value = true
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
val inputStream =
|
||||||
|
context.contentResolver.openInputStream(uri) ?: return@launch
|
||||||
|
val theme = inputStream.use {
|
||||||
|
val json = it.readBytes().toString(Charsets.UTF_8)
|
||||||
|
try {
|
||||||
|
Theme.fromJson(json)
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this@ImportThemeSheetVM.theme.value = theme
|
||||||
|
if (theme == null) {
|
||||||
|
error.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun importTheme(theme: Theme, apply: Boolean) {
|
||||||
|
themeRepository.createTheme(theme)
|
||||||
|
if (apply) {
|
||||||
|
dataStore.updateData {
|
||||||
|
it.toBuilder()
|
||||||
|
.setAppearance(
|
||||||
|
it.appearance.toBuilder()
|
||||||
|
.setThemeId(theme.id.toString())
|
||||||
|
).build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
package de.mm20.launcher2.ui.launcher
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.activity.compose.setContent
|
||||||
|
import de.mm20.launcher2.ui.base.BaseActivity
|
||||||
|
import de.mm20.launcher2.ui.base.ProvideSettings
|
||||||
|
import de.mm20.launcher2.ui.common.ImportThemeSheet
|
||||||
|
import de.mm20.launcher2.ui.overlays.OverlayHost
|
||||||
|
import de.mm20.launcher2.ui.theme.LauncherTheme
|
||||||
|
|
||||||
|
class ImportThemeActivity : BaseActivity() {
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
val uri = intent.data ?: return finish()
|
||||||
|
|
||||||
|
setContent {
|
||||||
|
LauncherTheme {
|
||||||
|
ProvideSettings {
|
||||||
|
OverlayHost {
|
||||||
|
ImportThemeSheet(
|
||||||
|
onDismiss = { finish() },
|
||||||
|
uri = uri,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -31,4 +31,24 @@
|
|||||||
<item name="windowNoTitle">true</item>
|
<item name="windowNoTitle">true</item>
|
||||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="DialogTheme" parent="BaseTheme">
|
||||||
|
<item name="windowActionBar">false</item>
|
||||||
|
<item name="windowNoTitle">true</item>
|
||||||
|
<item name="android:windowBackground">@android:color/transparent</item>
|
||||||
|
<item name="android:windowIsTranslucent">true</item>
|
||||||
|
<item name="android:windowFrame">@null</item>
|
||||||
|
<item name="android:windowContentOverlay">@null</item>
|
||||||
|
<item name="windowActionModeOverlay">true</item>
|
||||||
|
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
|
||||||
|
<item name="android:windowTranslucentStatus">false</item>
|
||||||
|
<item name="android:windowTranslucentNavigation">false</item>
|
||||||
|
<item name="android:enforceStatusBarContrast">false</item>
|
||||||
|
<item name="android:enforceNavigationBarContrast">false</item>
|
||||||
|
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||||
|
<item name="android:statusBarColor">#00000000</item>
|
||||||
|
<item name="android:navigationBarColor">#00000000</item>
|
||||||
|
<item name="android:activityOpenEnterAnimation">@android:anim/fade_in</item>
|
||||||
|
<item name="android:activityCloseExitAnimation">@android:anim/fade_out</item>
|
||||||
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
@ -24,6 +24,7 @@
|
|||||||
<!-- Shown in a snackbar after an item has been hidden. %1$s: label of the item -->
|
<!-- Shown in a snackbar after an item has been hidden. %1$s: label of the item -->
|
||||||
<string name="msg_item_hidden">%1$s has been hidden.</string>
|
<string name="msg_item_hidden">%1$s has been hidden.</string>
|
||||||
<string name="action_undo">Undo</string>
|
<string name="action_undo">Undo</string>
|
||||||
|
<string name="action_import">Import</string>
|
||||||
<!-- Delete something (a file or a web search shortcut) -->
|
<!-- Delete something (a file or a web search shortcut) -->
|
||||||
<string name="menu_delete">Delete</string>
|
<string name="menu_delete">Delete</string>
|
||||||
<string name="menu_remove">Remove</string>
|
<string name="menu_remove">Remove</string>
|
||||||
@ -839,4 +840,5 @@
|
|||||||
<string name="theme_color_scheme_palette_color">Palette</string>
|
<string name="theme_color_scheme_palette_color">Palette</string>
|
||||||
<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>
|
||||||
</resources>
|
</resources>
|
||||||
@ -1,6 +1,7 @@
|
|||||||
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
|
||||||
@ -53,5 +54,9 @@ fun Theme.toJson(): String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun Theme.Companion.fromJson(json: String): Theme {
|
fun Theme.Companion.fromJson(json: String): Theme {
|
||||||
return ThemeJson.decodeFromString(json)
|
return try {
|
||||||
|
ThemeJson.decodeFromString(json)
|
||||||
|
} catch (e: SerializationException) {
|
||||||
|
throw IllegalArgumentException(e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user