Add color scheme backup / restore

This commit is contained in:
MM20 2023-08-26 18:52:35 +02:00
parent f223e9307d
commit ff7a72f7c4
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
9 changed files with 75 additions and 8 deletions

View File

@ -162,6 +162,7 @@ fun RestoreBackupSheet(
BackupComponent.SearchActions -> Icons.Rounded.ArrowOutward
BackupComponent.Widgets -> Icons.Rounded.Widgets
BackupComponent.Customizations -> Icons.Rounded.Edit
BackupComponent.Themes -> Icons.Rounded.Palette
},
contentDescription = null
)
@ -173,6 +174,7 @@ fun RestoreBackupSheet(
BackupComponent.SearchActions -> R.string.backup_component_searchactions
BackupComponent.Widgets -> R.string.backup_component_widgets
BackupComponent.Customizations -> R.string.backup_component_customizations
BackupComponent.Themes -> R.string.backup_component_themes
}
),
style = MaterialTheme.typography.titleMedium,

View File

@ -133,6 +133,14 @@ fun CreateBackupSheet(
viewModel.toggleComponent(BackupComponent.SearchActions)
}
)
BackupableComponent(
title = stringResource(R.string.backup_component_themes),
icon = Icons.Rounded.Palette,
checked = components.contains(BackupComponent.Themes),
onCheckedChange = {
viewModel.toggleComponent(BackupComponent.Themes)
}
)
SmallMessage(
modifier = Modifier
.padding(top = 8.dp)

View File

@ -24,4 +24,10 @@ interface ThemeDao {
@Query("DELETE FROM Theme WHERE id = :id")
suspend fun delete(id: UUID)
@Query("DELETE FROM Theme")
suspend fun deleteAll()
@Insert
fun insertAll(themes: List<ThemeEntity>)
}

View File

@ -694,6 +694,7 @@
<string name="backup_component_settings">Settings</string>
<string name="backup_component_websearches">Web search shortcuts</string>
<string name="backup_component_searchactions">Quick actions</string>
<string name="backup_component_themes">Color schemes</string>
<string name="backup_component_widgets">Built-in widgets</string>
<string name="backup_component_customizations">Customizations</string>
<string name="backup_complete">The backup has been completed.</string>

View File

@ -1,18 +1,22 @@
package de.mm20.launcher2.themes
import android.content.Context
import android.util.Log
import de.mm20.launcher2.crashreporter.CrashReporter
import de.mm20.launcher2.database.AppDatabase
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.SerializationException
import kotlinx.serialization.encodeToString
import java.io.File
import java.lang.IllegalArgumentException
import java.util.UUID
class ThemeRepository(
@ -85,4 +89,38 @@ class ThemeRepository(
}
}
suspend fun export(toDir: File) = withContext(Dispatchers.IO) {
val dao = database.themeDao()
val themes = dao.getAll().first().map { Theme(it) }
val data = ThemeJson.encodeToString(themes)
val file = File(toDir, "themes.0000")
file.bufferedWriter().use {
it.write(data)
}
}
suspend fun import(fromDir: File) = withContext(Dispatchers.IO) {
val dao = database.themeDao()
dao.deleteAll()
val files =
fromDir.listFiles { _, name -> name.startsWith("themes.") }
?: return@withContext
for (file in files) {
val data = file.inputStream().reader().readText()
val themes: List<Theme> = try {
ThemeJson.decodeFromString(data)
} catch (e: SerializationException) {
CrashReporter.logException(e)
continue
} catch (e: IllegalArgumentException) {
CrashReporter.logException(e)
continue
}
dao.insertAll(themes.map { it.toEntity() })
}
}
}

View File

@ -46,5 +46,6 @@ dependencies {
implementation(project(":core:preferences"))
implementation(project(":core:ktx"))
implementation(project(":data:customattrs"))
implementation(project(":data:themes"))
}

View File

@ -5,11 +5,12 @@ enum class BackupComponent(val value: String) {
Favorites("favorites"),
Widgets("widgets2"),
Customizations("customizations"),
SearchActions("searchactions");
SearchActions("searchactions"),
Themes("themes");
companion object {
fun fromValue(value: String): BackupComponent? {
return values().firstOrNull { it.value == value }
return entries.firstOrNull { it.value == value }
}
}
}

View File

@ -9,6 +9,7 @@ import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.preferences.export
import de.mm20.launcher2.preferences.import
import de.mm20.launcher2.searchactions.SearchActionRepository
import de.mm20.launcher2.themes.ThemeRepository
import de.mm20.launcher2.widgets.WidgetRepository
import kotlinx.coroutines.*
import java.io.File
@ -25,6 +26,7 @@ class BackupManager(
private val widgetRepository: WidgetRepository,
private val searchActionRepository: SearchActionRepository,
private val customAttrsRepository: CustomAttributesRepository,
private val themesRepository: ThemeRepository,
) {
private val scope = CoroutineScope(Dispatchers.Default + Job())
@ -34,7 +36,7 @@ class BackupManager(
*/
suspend fun backup(
uri: Uri,
include: Set<BackupComponent> = BackupComponent.values().toSet()
include: Set<BackupComponent> = BackupComponent.entries.toSet()
) {
val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
@ -78,6 +80,10 @@ class BackupManager(
customAttrsRepository.export(backupDir)
}
if (include.contains(BackupComponent.Themes)) {
themesRepository.export(backupDir)
}
createArchive(backupDir, outputStream)
outputStream.close()
@ -118,6 +124,10 @@ class BackupManager(
if (include.contains(BackupComponent.Customizations)) {
customAttrsRepository.import(restoreDir)
}
if (include.contains(BackupComponent.Themes)) {
themesRepository.import(restoreDir)
}
}
}
job.join()
@ -188,7 +198,7 @@ class BackupManager(
*/
private const val BackupFormatMajor = 1
private const val BackupFormatMinor = 6
private const val BackupFormatMinor = 7
internal const val BackupFormat = "$BackupFormatMajor.$BackupFormatMinor"
}
}

View File

@ -4,5 +4,5 @@ import org.koin.android.ext.koin.androidContext
import org.koin.dsl.module
val backupModule = module {
single { BackupManager(androidContext(), get(), get(), get(), get(), get()) }
single { BackupManager(androidContext(), get(), get(), get(), get(), get(), get()) }
}