fix: 主题相关
This commit is contained in:
@@ -72,6 +72,13 @@ object ThemeImportExport {
|
||||
return saveThemeData(name, data)
|
||||
}
|
||||
|
||||
fun uniqueSavedThemeName(name: String): String {
|
||||
if (_savedThemes.none { it.name == name }) return name
|
||||
var index = 2
|
||||
while (_savedThemes.any { it.name == "$name $index" }) index++
|
||||
return "$name $index"
|
||||
}
|
||||
|
||||
private fun saveThemeData(name: String, data: ThemeExportData): SavedTheme {
|
||||
val file = File(baseDir, "$name.json")
|
||||
baseDir.mkdirs()
|
||||
@@ -82,19 +89,6 @@ object ThemeImportExport {
|
||||
return theme
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用已保存的主题
|
||||
*/
|
||||
fun applySavedTheme(theme: SavedTheme): Boolean {
|
||||
return try {
|
||||
applyToThemeConfig(theme.data)
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除已保存的主题
|
||||
*/
|
||||
@@ -305,7 +299,10 @@ object ThemeImportExport {
|
||||
/**
|
||||
* 将导出数据应用到当前配置
|
||||
*/
|
||||
fun applyToThemeConfig(data: ThemeExportData) {
|
||||
internal fun applyToThemeConfig(
|
||||
data: ThemeExportData,
|
||||
applyEmbeddedCoverAssets: Boolean = true,
|
||||
): AppliedThemeAssets {
|
||||
// 基础主题设置
|
||||
ThemeConfig.appTheme = data.appTheme
|
||||
ThemeConfig.themeMode = data.themeMode
|
||||
@@ -416,16 +413,31 @@ object ThemeImportExport {
|
||||
CoverConfig.coverInfoOrientation = data.coverInfoOrientation
|
||||
|
||||
// 应用嵌入的资源
|
||||
data.assets?.let { assets ->
|
||||
applyAssets(assets)
|
||||
}
|
||||
val embeddedAssets = data.assets?.let { assets ->
|
||||
applyAssets(
|
||||
assets = assets,
|
||||
applyCoverAssets = applyEmbeddedCoverAssets,
|
||||
)
|
||||
} ?: AppliedThemeAssets()
|
||||
return AppliedThemeAssets(
|
||||
lightCoverPaths = embeddedAssets.lightCoverPaths.ifEmpty {
|
||||
data.coverDefaultImage.toCoverPaths()
|
||||
},
|
||||
darkCoverPaths = embeddedAssets.darkCoverPaths.ifEmpty {
|
||||
data.coverDefaultImageDark.toCoverPaths()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun applyAssets(assets: Map<String, String>) {
|
||||
private fun applyAssets(
|
||||
assets: Map<String, String>,
|
||||
applyCoverAssets: Boolean,
|
||||
): AppliedThemeAssets {
|
||||
val coverPaths = mutableMapOf<String, MutableList<String>>()
|
||||
|
||||
assets.forEach { (key, base64) ->
|
||||
if (base64.isBlank()) return@forEach
|
||||
if (!applyCoverAssets && key.startsWith("coverDefaultImage")) return@forEach
|
||||
try {
|
||||
val bytes = EncoderUtils.base64DecodeToByteArray(base64)
|
||||
val destFile = when {
|
||||
@@ -493,6 +505,10 @@ object ThemeImportExport {
|
||||
coverPaths["coverDefaultImageDark"]?.let { paths ->
|
||||
CoverConfig.defaultCoverDark = paths.joinToString(",")
|
||||
}
|
||||
return AppliedThemeAssets(
|
||||
lightCoverPaths = coverPaths["coverDefaultImage"].orEmpty(),
|
||||
darkCoverPaths = coverPaths["coverDefaultImageDark"].orEmpty(),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -507,16 +523,22 @@ object ThemeImportExport {
|
||||
* 从JSON字符串导入主题
|
||||
*/
|
||||
fun importFromJson(json: String): Boolean {
|
||||
return importFromJsonWithAssets(json) != null
|
||||
}
|
||||
|
||||
internal fun importFromJsonWithAssets(json: String): AppliedThemeAssets? {
|
||||
return try {
|
||||
val data = parseThemeData(json) ?: return false
|
||||
val data = parseThemeData(json) ?: return null
|
||||
applyToThemeConfig(data)
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
false
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.toCoverPaths(): List<String> =
|
||||
split(",").map(String::trim).filter(String::isNotEmpty)
|
||||
|
||||
private fun parseThemeData(json: String): ThemeExportData? {
|
||||
val root = JsonParser.parseString(json).asJsonObject
|
||||
return when {
|
||||
@@ -761,6 +783,7 @@ data class ThemeExportData(
|
||||
val bgImageBlurring: Int = 0,
|
||||
val bgImageNBlurring: Int = 0,
|
||||
val appFontPath: String? = null,
|
||||
val selectedCoverAlbumId: String? = null,
|
||||
|
||||
// 封面配置 (CoverConfig)
|
||||
val coverLoadOnlyWifi: Boolean = false,
|
||||
@@ -792,3 +815,8 @@ data class SavedTheme(
|
||||
val name: String,
|
||||
val data: ThemeExportData
|
||||
)
|
||||
|
||||
internal data class AppliedThemeAssets(
|
||||
val lightCoverPaths: List<String> = emptyList(),
|
||||
val darkCoverPaths: List<String> = emptyList(),
|
||||
)
|
||||
|
||||
@@ -2,14 +2,13 @@ package io.legado.app.help.config
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.provider.OpenableColumns
|
||||
import androidx.annotation.Keep
|
||||
import com.google.gson.JsonParser
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import io.legado.app.R
|
||||
import io.legado.app.domain.model.CoverAlbumImageInput
|
||||
import io.legado.app.domain.usecase.CoverAlbumUseCase
|
||||
import io.legado.app.ui.config.coverConfig.CoverConfig
|
||||
import io.legado.app.ui.config.themeConfig.ThemeConfig
|
||||
import io.legado.app.utils.GSON
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -60,7 +59,10 @@ class ThemePackageManager(
|
||||
context.contentResolver.openOutputStream(uri)?.use { output ->
|
||||
ZipOutputStream(BufferedOutputStream(output)).use { zip ->
|
||||
val assetEntries = exportAssets(zip, rawConfig)
|
||||
val coverData = exportCoverAlbums(zip)
|
||||
val coverData = exportCoverAlbums(
|
||||
zip = zip,
|
||||
preferredAlbumId = rawConfig.selectedCoverAlbumId,
|
||||
)
|
||||
val manifest = ThemePackageManifest(
|
||||
formatVersion = FORMAT_VERSION,
|
||||
name = themeName,
|
||||
@@ -126,6 +128,10 @@ class ThemePackageManager(
|
||||
)
|
||||
)
|
||||
coverAlbumUseCase.selectAlbum(selectedAlbumId)
|
||||
saveImportedTheme(
|
||||
name = importedThemeName(uri, manifest.name),
|
||||
selectedCoverAlbumId = selectedAlbumId,
|
||||
)
|
||||
} catch (error: CancellationException) {
|
||||
throw error
|
||||
} catch (error: Exception) {
|
||||
@@ -145,13 +151,61 @@ class ThemePackageManager(
|
||||
val json = context.contentResolver.openInputStream(uri)?.use {
|
||||
it.bufferedReader().readText()
|
||||
} ?: error("无法读取旧主题配置")
|
||||
check(ThemeImportExport.importFromJson(json)) {
|
||||
"旧主题配置格式无效或字段已被混淆"
|
||||
}
|
||||
importLegacyCoverAlbums()
|
||||
val appliedAssets = ThemeImportExport.importFromJsonWithAssets(json)
|
||||
?: error("旧主题配置格式无效或字段已被混淆")
|
||||
val name = importedThemeName(uri)
|
||||
val selectedCoverAlbumId = importLegacyCoverAlbums(
|
||||
albumName = name,
|
||||
appliedAssets = appliedAssets,
|
||||
)
|
||||
saveImportedTheme(
|
||||
name = name,
|
||||
selectedCoverAlbumId = selectedCoverAlbumId,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun saveTheme(
|
||||
name: String,
|
||||
data: ThemeExportData = ThemeImportExport.exportFromCurrent(),
|
||||
): SavedTheme {
|
||||
val selectedCoverAlbumId = data.selectedCoverAlbumId
|
||||
?: coverAlbumUseCase.selection.value.albumId
|
||||
return ThemeImportExport.saveCurrentAsTheme(
|
||||
name = name,
|
||||
data = data.copy(selectedCoverAlbumId = selectedCoverAlbumId),
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun applySavedTheme(theme: SavedTheme): Result<Unit> =
|
||||
withContext(Dispatchers.IO) {
|
||||
runSuspendCatching {
|
||||
val savedAlbumId = theme.data.selectedCoverAlbumId
|
||||
?.takeIf { id -> coverAlbumUseCase.albums.value.any { it.id == id } }
|
||||
val appliedAssets = ThemeImportExport.applyToThemeConfig(
|
||||
data = theme.data,
|
||||
applyEmbeddedCoverAssets = savedAlbumId == null,
|
||||
)
|
||||
val selectedCoverAlbumId = if (savedAlbumId != null) {
|
||||
coverAlbumUseCase.selectAlbum(savedAlbumId)
|
||||
savedAlbumId
|
||||
} else {
|
||||
importLegacyCoverAlbums(
|
||||
albumName = theme.name,
|
||||
appliedAssets = appliedAssets,
|
||||
)
|
||||
}
|
||||
if (selectedCoverAlbumId != theme.data.selectedCoverAlbumId) {
|
||||
ThemeImportExport.saveCurrentAsTheme(
|
||||
name = theme.name,
|
||||
data = theme.data.copy(
|
||||
selectedCoverAlbumId = selectedCoverAlbumId,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun exportAssets(
|
||||
zip: ZipOutputStream,
|
||||
config: ThemeExportData,
|
||||
@@ -183,10 +237,15 @@ class ThemePackageManager(
|
||||
return result
|
||||
}
|
||||
|
||||
private fun exportCoverAlbums(zip: ZipOutputStream): ExportedCoverData {
|
||||
val selection = coverAlbumUseCase.selection.value
|
||||
private fun exportCoverAlbums(
|
||||
zip: ZipOutputStream,
|
||||
preferredAlbumId: String?,
|
||||
): ExportedCoverData {
|
||||
val albumsById = coverAlbumUseCase.albums.value.associateBy { it.id }
|
||||
val selectedIds = listOfNotNull(selection.albumId)
|
||||
val selectedAlbumId = preferredAlbumId
|
||||
?.takeIf(albumsById::containsKey)
|
||||
?: coverAlbumUseCase.selection.value.albumId
|
||||
val selectedIds = listOfNotNull(selectedAlbumId)
|
||||
val refById = selectedIds.associateWith { "album_0" }
|
||||
val exportedAlbums = selectedIds.mapNotNull { albumId ->
|
||||
val album = albumsById[albumId] ?: return@mapNotNull null
|
||||
@@ -217,7 +276,7 @@ class ThemePackageManager(
|
||||
return ExportedCoverData(
|
||||
albums = exportedAlbums,
|
||||
selection = ThemePackageCoverSelection(
|
||||
albumRef = selection.albumId?.let(refById::get),
|
||||
albumRef = selectedAlbumId?.let(refById::get),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -289,15 +348,18 @@ class ThemePackageManager(
|
||||
return result
|
||||
}
|
||||
|
||||
private suspend fun importLegacyCoverAlbums() {
|
||||
private suspend fun importLegacyCoverAlbums(
|
||||
albumName: String,
|
||||
appliedAssets: AppliedThemeAssets,
|
||||
): String? {
|
||||
val importedIds = mutableListOf<String>()
|
||||
try {
|
||||
val lightFiles = CoverConfig.defaultCover.toExistingFiles()
|
||||
val darkFiles = CoverConfig.defaultCoverDark.toExistingFiles()
|
||||
val lightFiles = appliedAssets.lightCoverPaths.toExistingFiles()
|
||||
val darkFiles = appliedAssets.darkCoverPaths.toExistingFiles()
|
||||
val albumId = if (lightFiles.isNotEmpty() || darkFiles.isNotEmpty()) {
|
||||
val existingNames = coverAlbumUseCase.albums.value.mapTo(mutableSetOf()) { it.name }
|
||||
val name = uniqueImportedAlbumName(
|
||||
context.getString(R.string.default_cover),
|
||||
albumName,
|
||||
existingNames,
|
||||
)
|
||||
coverAlbumUseCase.importAlbum(
|
||||
@@ -311,6 +373,7 @@ class ThemePackageManager(
|
||||
null
|
||||
}
|
||||
coverAlbumUseCase.selectAlbum(albumId)
|
||||
return albumId
|
||||
} catch (error: CancellationException) {
|
||||
throw error
|
||||
} catch (error: Exception) {
|
||||
@@ -321,6 +384,43 @@ class ThemePackageManager(
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveImportedTheme(
|
||||
name: String,
|
||||
selectedCoverAlbumId: String?,
|
||||
) {
|
||||
saveTheme(
|
||||
name = ThemeImportExport.uniqueSavedThemeName(name),
|
||||
data = ThemeImportExport.exportFromCurrent().copy(
|
||||
selectedCoverAlbumId = selectedCoverAlbumId,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun importedThemeName(
|
||||
uri: Uri,
|
||||
packageName: String? = null,
|
||||
): String {
|
||||
val displayName = context.contentResolver.query(
|
||||
uri,
|
||||
arrayOf(OpenableColumns.DISPLAY_NAME),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
)?.use { cursor ->
|
||||
if (cursor.moveToFirst()) cursor.getString(0) else null
|
||||
}
|
||||
val rawName = packageName
|
||||
?.takeIf(String::isNotBlank)
|
||||
?: displayName?.substringBeforeLast('.')
|
||||
?: uri.lastPathSegment?.substringBeforeLast('.')
|
||||
?: context.getString(R.string.import_theme)
|
||||
return rawName
|
||||
.replace(Regex("""[\\/:*?"<>|]"""), "_")
|
||||
.trim()
|
||||
.trim('.')
|
||||
.ifBlank { context.getString(R.string.import_theme) }
|
||||
}
|
||||
|
||||
private fun extractPackage(uri: Uri, root: File) {
|
||||
root.mkdirs()
|
||||
var entryCount = 0
|
||||
@@ -410,6 +510,7 @@ class ThemePackageManager(
|
||||
appFontPath = null,
|
||||
coverDefaultImage = "",
|
||||
coverDefaultImageDark = "",
|
||||
selectedCoverAlbumId = null,
|
||||
assets = null,
|
||||
)
|
||||
|
||||
@@ -456,9 +557,8 @@ class ThemePackageManager(
|
||||
return entryBytes
|
||||
}
|
||||
|
||||
private fun String.toExistingFiles(): List<File> =
|
||||
split(",")
|
||||
.map(String::trim)
|
||||
private fun List<String>.toExistingFiles(): List<File> =
|
||||
map(String::trim)
|
||||
.filter(String::isNotEmpty)
|
||||
.map(::File)
|
||||
.filter(File::isFile)
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
package io.legado.app.ui.config.customTheme
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -18,6 +16,7 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
@@ -33,13 +32,13 @@ import io.legado.app.ui.config.themeConfig.ThemeConfig
|
||||
import io.legado.app.ui.theme.adaptiveContentPadding
|
||||
import io.legado.app.ui.widget.components.AppScaffold
|
||||
import io.legado.app.ui.widget.components.SplicedColumnGroup
|
||||
import io.legado.app.ui.widget.components.topbar.TopBarNavigationButton
|
||||
import io.legado.app.ui.widget.components.settingItem.DropdownListSettingItem
|
||||
import io.legado.app.ui.widget.components.dialog.ColorPickerSheet
|
||||
import io.legado.app.ui.widget.components.settingItem.ClickableSettingItem
|
||||
import io.legado.app.ui.widget.components.settingItem.DropdownListSettingItem
|
||||
import io.legado.app.ui.widget.components.settingItem.SwitchSettingItem
|
||||
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
|
||||
import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
|
||||
import io.legado.app.ui.widget.components.topbar.TopBarNavigationButton
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -140,7 +139,7 @@ fun CustomThemeScreen(
|
||||
}
|
||||
} else {
|
||||
item {
|
||||
SplicedColumnGroup(title = stringResource(R.string.custom_theme)) {
|
||||
SplicedColumnGroup(title = stringResource(R.string.custom_theme_colors)) {
|
||||
ClickableSettingItem(
|
||||
title = stringResource(R.string.seed_color),
|
||||
description = stringResource(R.string.day),
|
||||
|
||||
@@ -322,6 +322,11 @@ fun ThemeConfigScreen(
|
||||
}
|
||||
)
|
||||
}
|
||||
ClickableSettingItem(
|
||||
title = stringResource(R.string.theme_pack),
|
||||
description = stringResource(R.string.theme_pack_s),
|
||||
onClick = onNavigateToThemeManage
|
||||
)
|
||||
}
|
||||
|
||||
SplicedColumnGroup {
|
||||
@@ -331,7 +336,7 @@ fun ThemeConfigScreen(
|
||||
)
|
||||
if (selectedTheme == "12" && (!isMiuixEngine || useMiuixMonet)) {
|
||||
ClickableSettingItem(
|
||||
title = stringResource(R.string.custom_theme),
|
||||
title = stringResource(R.string.custom_theme_colors),
|
||||
onClick = onNavigateToCustomTheme
|
||||
)
|
||||
}
|
||||
@@ -803,16 +808,6 @@ fun ThemeConfigScreen(
|
||||
}
|
||||
}
|
||||
|
||||
// Theme management
|
||||
item {
|
||||
SplicedColumnGroup(title = stringResource(R.string.theme_pack)) {
|
||||
ClickableSettingItem(
|
||||
title = stringResource(R.string.theme_pack),
|
||||
description = stringResource(R.string.theme_pack_s),
|
||||
onClick = onNavigateToThemeManage
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -115,6 +115,14 @@ fun ThemeManageScreen(
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.effects.collectLatest { effect ->
|
||||
when (effect) {
|
||||
ThemeManageEffect.SavedThemesChanged -> {
|
||||
savedThemesVersion++
|
||||
}
|
||||
|
||||
ThemeManageEffect.RestartRequired -> {
|
||||
showRestartDialog = true
|
||||
}
|
||||
|
||||
is ThemeManageEffect.ShowResult -> {
|
||||
val message = buildString {
|
||||
append(context.getString(effect.messageRes))
|
||||
@@ -124,6 +132,9 @@ fun ThemeManageScreen(
|
||||
}
|
||||
}
|
||||
context.toastOnUi(message)
|
||||
if (effect.savedThemesChanged) {
|
||||
savedThemesVersion++
|
||||
}
|
||||
if (effect.restartRequired) {
|
||||
showRestartDialog = true
|
||||
}
|
||||
@@ -249,8 +260,7 @@ fun ThemeManageScreen(
|
||||
confirmText = stringResource(R.string.theme_manage_save),
|
||||
onConfirm = {
|
||||
if (newThemeName.isNotBlank()) {
|
||||
ThemeImportExport.saveCurrentAsTheme(newThemeName)
|
||||
savedThemesVersion++
|
||||
viewModel.onIntent(ThemeManageIntent.SaveTheme(newThemeName))
|
||||
showSaveDialog = false
|
||||
}
|
||||
},
|
||||
@@ -275,8 +285,7 @@ fun ThemeManageScreen(
|
||||
confirmText = stringResource(R.string.theme_manage_apply),
|
||||
onConfirm = {
|
||||
applyTarget?.let { theme ->
|
||||
ThemeImportExport.applySavedTheme(theme)
|
||||
showRestartDialog = true
|
||||
viewModel.onIntent(ThemeManageIntent.ApplySavedTheme(theme))
|
||||
}
|
||||
applyTarget = null
|
||||
},
|
||||
@@ -310,11 +319,13 @@ fun ThemeManageScreen(
|
||||
themeName = editTarget?.name ?: "",
|
||||
onDismissRequest = { editTarget = null },
|
||||
onSave = { newName, newData ->
|
||||
editTarget?.let { old ->
|
||||
ThemeImportExport.deleteSavedTheme(old)
|
||||
}
|
||||
ThemeImportExport.saveCurrentAsTheme(newName, newData)
|
||||
savedThemesVersion++
|
||||
viewModel.onIntent(
|
||||
ThemeManageIntent.SaveTheme(
|
||||
name = newName,
|
||||
data = newData,
|
||||
replacedTheme = editTarget,
|
||||
)
|
||||
)
|
||||
editTarget = null
|
||||
}
|
||||
)
|
||||
|
||||
@@ -5,8 +5,11 @@ import androidx.annotation.StringRes
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import io.legado.app.R
|
||||
import io.legado.app.help.config.SavedTheme
|
||||
import io.legado.app.help.config.ThemeExportData
|
||||
import io.legado.app.help.config.ThemeImportExport
|
||||
import io.legado.app.help.config.ThemePackageManager
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -23,6 +26,49 @@ class ThemeManageViewModel(
|
||||
is ThemeManageIntent.ExportPackage -> exportPackage(intent)
|
||||
is ThemeManageIntent.ImportPackage -> importPackage(intent.uri)
|
||||
is ThemeManageIntent.ImportLegacyJson -> importLegacyJson(intent.uri)
|
||||
is ThemeManageIntent.SaveTheme -> saveTheme(intent)
|
||||
is ThemeManageIntent.ApplySavedTheme -> applySavedTheme(intent.theme)
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveTheme(intent: ThemeManageIntent.SaveTheme) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val result = runCatching {
|
||||
themePackageManager.saveTheme(
|
||||
name = intent.name,
|
||||
data = intent.data ?: ThemeImportExport.exportFromCurrent(),
|
||||
)
|
||||
intent.replacedTheme
|
||||
?.takeIf { it.name != intent.name }
|
||||
?.let(ThemeImportExport::deleteSavedTheme)
|
||||
}
|
||||
if (result.isSuccess) {
|
||||
_effects.emit(ThemeManageEffect.SavedThemesChanged)
|
||||
} else {
|
||||
_effects.emit(
|
||||
ThemeManageEffect.ShowResult(
|
||||
messageRes = R.string.theme_manage_save_failed,
|
||||
detail = result.exceptionOrNull()?.localizedMessage,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun applySavedTheme(theme: SavedTheme) {
|
||||
viewModelScope.launch {
|
||||
val result = themePackageManager.applySavedTheme(theme)
|
||||
if (result.isSuccess) {
|
||||
_effects.emit(ThemeManageEffect.SavedThemesChanged)
|
||||
_effects.emit(ThemeManageEffect.RestartRequired)
|
||||
} else {
|
||||
_effects.emit(
|
||||
ThemeManageEffect.ShowResult(
|
||||
messageRes = R.string.theme_manage_apply_failed,
|
||||
detail = result.exceptionOrNull()?.localizedMessage,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +110,7 @@ class ThemeManageViewModel(
|
||||
ThemeManageEffect.ShowResult(
|
||||
messageRes = R.string.theme_manage_import_success,
|
||||
restartRequired = true,
|
||||
savedThemesChanged = true,
|
||||
)
|
||||
} else {
|
||||
ThemeManageEffect.ShowResult(
|
||||
@@ -84,12 +131,23 @@ sealed interface ThemeManageIntent {
|
||||
|
||||
data class ImportPackage(val uri: String) : ThemeManageIntent
|
||||
data class ImportLegacyJson(val uri: String) : ThemeManageIntent
|
||||
data class SaveTheme(
|
||||
val name: String,
|
||||
val data: ThemeExportData? = null,
|
||||
val replacedTheme: SavedTheme? = null,
|
||||
) : ThemeManageIntent
|
||||
|
||||
data class ApplySavedTheme(val theme: SavedTheme) : ThemeManageIntent
|
||||
}
|
||||
|
||||
sealed interface ThemeManageEffect {
|
||||
data object SavedThemesChanged : ThemeManageEffect
|
||||
data object RestartRequired : ThemeManageEffect
|
||||
|
||||
data class ShowResult(
|
||||
@param:StringRes val messageRes: Int,
|
||||
val detail: String? = null,
|
||||
val restartRequired: Boolean = false,
|
||||
val savedThemesChanged: Boolean = false,
|
||||
) : ThemeManageEffect
|
||||
}
|
||||
|
||||
@@ -1436,6 +1436,8 @@
|
||||
<string name="theme_manage_export_failed">主题导出失败</string>
|
||||
<string name="theme_manage_import_success">主题导入成功,部分设置需要重启生效</string>
|
||||
<string name="theme_manage_import_failed">主题导入失败</string>
|
||||
<string name="theme_manage_save_failed">主题保存失败</string>
|
||||
<string name="theme_manage_apply_failed">主题应用失败</string>
|
||||
<string name="theme_manage_save_current">保存当前设置</string>
|
||||
<string name="theme_manage_save_current_summary">保存当前主题配置为新主题</string>
|
||||
<string name="theme_manage_export_current">导出当前主题</string>
|
||||
|
||||
@@ -2114,6 +2114,8 @@
|
||||
<string name="theme_manage_import_config_summary">從舊版 JSON 檔案匯入主題設定</string>
|
||||
<string name="theme_manage_import_failed">主題匯入失敗</string>
|
||||
<string name="theme_manage_import_success">主題已匯入。部分更改需要重啟。</string>
|
||||
<string name="theme_manage_save_failed">主題儲存失敗</string>
|
||||
<string name="theme_manage_apply_failed">主題套用失敗</string>
|
||||
<string name="theme_manage_label_container_color">標籤容器色</string>
|
||||
<string name="theme_manage_label_visibility">標籤可見性</string>
|
||||
<string name="theme_manage_name_hint">輸入主題名稱</string>
|
||||
|
||||
@@ -1583,6 +1583,8 @@
|
||||
<string name="theme_manage_export_failed">無法匯出主題</string>
|
||||
<string name="theme_manage_import_success">主題已匯入。部分變更需要重新啟動。</string>
|
||||
<string name="theme_manage_import_failed">無法匯入主題</string>
|
||||
<string name="theme_manage_save_failed">無法儲存主題</string>
|
||||
<string name="theme_manage_apply_failed">無法套用主題</string>
|
||||
<string name="theme_manage_save_current">儲存目前主題</string>
|
||||
<string name="theme_manage_save_current_summary">將目前外觀設定儲存為主題</string>
|
||||
<string name="theme_manage_export_current">匯出目前主題</string>
|
||||
|
||||
@@ -1471,6 +1471,8 @@
|
||||
<string name="theme_manage_export_failed">Could not export theme</string>
|
||||
<string name="theme_manage_import_success">Theme imported. Some changes require a restart.</string>
|
||||
<string name="theme_manage_import_failed">Could not import theme</string>
|
||||
<string name="theme_manage_save_failed">Could not save theme</string>
|
||||
<string name="theme_manage_apply_failed">Could not apply theme</string>
|
||||
<string name="theme_manage_save_current">Save Current Theme</string>
|
||||
<string name="theme_manage_save_current_summary">Save the current appearance settings as a theme</string>
|
||||
<string name="theme_manage_export_current">Export Current Theme</string>
|
||||
|
||||
Reference in New Issue
Block a user