Add color scheme backup / restore
This commit is contained in:
parent
f223e9307d
commit
ff7a72f7c4
@ -162,6 +162,7 @@ fun RestoreBackupSheet(
|
|||||||
BackupComponent.SearchActions -> Icons.Rounded.ArrowOutward
|
BackupComponent.SearchActions -> Icons.Rounded.ArrowOutward
|
||||||
BackupComponent.Widgets -> Icons.Rounded.Widgets
|
BackupComponent.Widgets -> Icons.Rounded.Widgets
|
||||||
BackupComponent.Customizations -> Icons.Rounded.Edit
|
BackupComponent.Customizations -> Icons.Rounded.Edit
|
||||||
|
BackupComponent.Themes -> Icons.Rounded.Palette
|
||||||
},
|
},
|
||||||
contentDescription = null
|
contentDescription = null
|
||||||
)
|
)
|
||||||
@ -173,6 +174,7 @@ fun RestoreBackupSheet(
|
|||||||
BackupComponent.SearchActions -> R.string.backup_component_searchactions
|
BackupComponent.SearchActions -> R.string.backup_component_searchactions
|
||||||
BackupComponent.Widgets -> R.string.backup_component_widgets
|
BackupComponent.Widgets -> R.string.backup_component_widgets
|
||||||
BackupComponent.Customizations -> R.string.backup_component_customizations
|
BackupComponent.Customizations -> R.string.backup_component_customizations
|
||||||
|
BackupComponent.Themes -> R.string.backup_component_themes
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
|||||||
@ -133,6 +133,14 @@ fun CreateBackupSheet(
|
|||||||
viewModel.toggleComponent(BackupComponent.SearchActions)
|
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(
|
SmallMessage(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(top = 8.dp)
|
.padding(top = 8.dp)
|
||||||
|
|||||||
@ -24,4 +24,10 @@ interface ThemeDao {
|
|||||||
|
|
||||||
@Query("DELETE FROM Theme WHERE id = :id")
|
@Query("DELETE FROM Theme WHERE id = :id")
|
||||||
suspend fun delete(id: UUID)
|
suspend fun delete(id: UUID)
|
||||||
|
|
||||||
|
@Query("DELETE FROM Theme")
|
||||||
|
suspend fun deleteAll()
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
fun insertAll(themes: List<ThemeEntity>)
|
||||||
}
|
}
|
||||||
@ -694,6 +694,7 @@
|
|||||||
<string name="backup_component_settings">Settings</string>
|
<string name="backup_component_settings">Settings</string>
|
||||||
<string name="backup_component_websearches">Web search shortcuts</string>
|
<string name="backup_component_websearches">Web search shortcuts</string>
|
||||||
<string name="backup_component_searchactions">Quick actions</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_widgets">Built-in widgets</string>
|
||||||
<string name="backup_component_customizations">Customizations</string>
|
<string name="backup_component_customizations">Customizations</string>
|
||||||
<string name="backup_complete">The backup has been completed.</string>
|
<string name="backup_complete">The backup has been completed.</string>
|
||||||
|
|||||||
@ -1,18 +1,22 @@
|
|||||||
package de.mm20.launcher2.themes
|
package de.mm20.launcher2.themes
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.Log
|
import de.mm20.launcher2.crashreporter.CrashReporter
|
||||||
import de.mm20.launcher2.database.AppDatabase
|
import de.mm20.launcher2.database.AppDatabase
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.combine
|
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.launch
|
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
|
import java.util.UUID
|
||||||
|
|
||||||
class ThemeRepository(
|
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() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -46,5 +46,6 @@ dependencies {
|
|||||||
implementation(project(":core:preferences"))
|
implementation(project(":core:preferences"))
|
||||||
implementation(project(":core:ktx"))
|
implementation(project(":core:ktx"))
|
||||||
implementation(project(":data:customattrs"))
|
implementation(project(":data:customattrs"))
|
||||||
|
implementation(project(":data:themes"))
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -5,11 +5,12 @@ enum class BackupComponent(val value: String) {
|
|||||||
Favorites("favorites"),
|
Favorites("favorites"),
|
||||||
Widgets("widgets2"),
|
Widgets("widgets2"),
|
||||||
Customizations("customizations"),
|
Customizations("customizations"),
|
||||||
SearchActions("searchactions");
|
SearchActions("searchactions"),
|
||||||
|
Themes("themes");
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun fromValue(value: String): BackupComponent? {
|
fun fromValue(value: String): BackupComponent? {
|
||||||
return values().firstOrNull { it.value == value }
|
return entries.firstOrNull { it.value == value }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -9,6 +9,7 @@ import de.mm20.launcher2.preferences.LauncherDataStore
|
|||||||
import de.mm20.launcher2.preferences.export
|
import de.mm20.launcher2.preferences.export
|
||||||
import de.mm20.launcher2.preferences.import
|
import de.mm20.launcher2.preferences.import
|
||||||
import de.mm20.launcher2.searchactions.SearchActionRepository
|
import de.mm20.launcher2.searchactions.SearchActionRepository
|
||||||
|
import de.mm20.launcher2.themes.ThemeRepository
|
||||||
import de.mm20.launcher2.widgets.WidgetRepository
|
import de.mm20.launcher2.widgets.WidgetRepository
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@ -25,6 +26,7 @@ class BackupManager(
|
|||||||
private val widgetRepository: WidgetRepository,
|
private val widgetRepository: WidgetRepository,
|
||||||
private val searchActionRepository: SearchActionRepository,
|
private val searchActionRepository: SearchActionRepository,
|
||||||
private val customAttrsRepository: CustomAttributesRepository,
|
private val customAttrsRepository: CustomAttributesRepository,
|
||||||
|
private val themesRepository: ThemeRepository,
|
||||||
) {
|
) {
|
||||||
private val scope = CoroutineScope(Dispatchers.Default + Job())
|
private val scope = CoroutineScope(Dispatchers.Default + Job())
|
||||||
|
|
||||||
@ -34,7 +36,7 @@ class BackupManager(
|
|||||||
*/
|
*/
|
||||||
suspend fun backup(
|
suspend fun backup(
|
||||||
uri: Uri,
|
uri: Uri,
|
||||||
include: Set<BackupComponent> = BackupComponent.values().toSet()
|
include: Set<BackupComponent> = BackupComponent.entries.toSet()
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
|
val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
|
||||||
@ -78,6 +80,10 @@ class BackupManager(
|
|||||||
customAttrsRepository.export(backupDir)
|
customAttrsRepository.export(backupDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (include.contains(BackupComponent.Themes)) {
|
||||||
|
themesRepository.export(backupDir)
|
||||||
|
}
|
||||||
|
|
||||||
createArchive(backupDir, outputStream)
|
createArchive(backupDir, outputStream)
|
||||||
outputStream.close()
|
outputStream.close()
|
||||||
|
|
||||||
@ -118,6 +124,10 @@ class BackupManager(
|
|||||||
if (include.contains(BackupComponent.Customizations)) {
|
if (include.contains(BackupComponent.Customizations)) {
|
||||||
customAttrsRepository.import(restoreDir)
|
customAttrsRepository.import(restoreDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (include.contains(BackupComponent.Themes)) {
|
||||||
|
themesRepository.import(restoreDir)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
job.join()
|
job.join()
|
||||||
@ -188,7 +198,7 @@ class BackupManager(
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
private const val BackupFormatMajor = 1
|
private const val BackupFormatMajor = 1
|
||||||
private const val BackupFormatMinor = 6
|
private const val BackupFormatMinor = 7
|
||||||
internal const val BackupFormat = "$BackupFormatMajor.$BackupFormatMinor"
|
internal const val BackupFormat = "$BackupFormatMajor.$BackupFormatMinor"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,5 +4,5 @@ import org.koin.android.ext.koin.androidContext
|
|||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
val backupModule = module {
|
val backupModule = module {
|
||||||
single { BackupManager(androidContext(), get(), get(), get(), get(), get()) }
|
single { BackupManager(androidContext(), get(), get(), get(), get(), get(), get()) }
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user