diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/common/RestoreBackupSheet.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/common/RestoreBackupSheet.kt index 02ecd6e5..a5854223 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/common/RestoreBackupSheet.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/common/RestoreBackupSheet.kt @@ -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, diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/backup/CreateBackupSheet.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/backup/CreateBackupSheet.kt index 4acd9860..04206270 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/backup/CreateBackupSheet.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/backup/CreateBackupSheet.kt @@ -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) diff --git a/core/database/src/main/java/de/mm20/launcher2/database/daos/ThemeDao.kt b/core/database/src/main/java/de/mm20/launcher2/database/daos/ThemeDao.kt index ea350d24..6f6bc310 100644 --- a/core/database/src/main/java/de/mm20/launcher2/database/daos/ThemeDao.kt +++ b/core/database/src/main/java/de/mm20/launcher2/database/daos/ThemeDao.kt @@ -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) } \ No newline at end of file diff --git a/core/i18n/src/main/res/values/strings.xml b/core/i18n/src/main/res/values/strings.xml index 7c33ee01..a7c96cef 100644 --- a/core/i18n/src/main/res/values/strings.xml +++ b/core/i18n/src/main/res/values/strings.xml @@ -694,6 +694,7 @@ Settings Web search shortcuts Quick actions + Color schemes Built-in widgets Customizations The backup has been completed. diff --git a/data/themes/src/main/java/de/mm20/launcher2/themes/ThemeRepository.kt b/data/themes/src/main/java/de/mm20/launcher2/themes/ThemeRepository.kt index f20e53ae..52115af6 100644 --- a/data/themes/src/main/java/de/mm20/launcher2/themes/ThemeRepository.kt +++ b/data/themes/src/main/java/de/mm20/launcher2/themes/ThemeRepository.kt @@ -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 = try { + ThemeJson.decodeFromString(data) + } catch (e: SerializationException) { + CrashReporter.logException(e) + continue + } catch (e: IllegalArgumentException) { + CrashReporter.logException(e) + continue + } + dao.insertAll(themes.map { it.toEntity() }) + } + } + } \ No newline at end of file diff --git a/services/backup/build.gradle.kts b/services/backup/build.gradle.kts index b0154b02..61f6ad78 100644 --- a/services/backup/build.gradle.kts +++ b/services/backup/build.gradle.kts @@ -46,5 +46,6 @@ dependencies { implementation(project(":core:preferences")) implementation(project(":core:ktx")) implementation(project(":data:customattrs")) + implementation(project(":data:themes")) } \ No newline at end of file diff --git a/services/backup/src/main/java/de/mm20/launcher2/backup/BackupComponent.kt b/services/backup/src/main/java/de/mm20/launcher2/backup/BackupComponent.kt index 2a8c0d3a..50ae09f9 100644 --- a/services/backup/src/main/java/de/mm20/launcher2/backup/BackupComponent.kt +++ b/services/backup/src/main/java/de/mm20/launcher2/backup/BackupComponent.kt @@ -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 } } } } \ No newline at end of file diff --git a/services/backup/src/main/java/de/mm20/launcher2/backup/BackupManager.kt b/services/backup/src/main/java/de/mm20/launcher2/backup/BackupManager.kt index 9dc38d51..b765d4b6 100644 --- a/services/backup/src/main/java/de/mm20/launcher2/backup/BackupManager.kt +++ b/services/backup/src/main/java/de/mm20/launcher2/backup/BackupManager.kt @@ -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.values().toSet() + include: Set = 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" } } diff --git a/services/backup/src/main/java/de/mm20/launcher2/backup/Module.kt b/services/backup/src/main/java/de/mm20/launcher2/backup/Module.kt index 1a94ceda..904f6299 100644 --- a/services/backup/src/main/java/de/mm20/launcher2/backup/Module.kt +++ b/services/backup/src/main/java/de/mm20/launcher2/backup/Module.kt @@ -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()) } } \ No newline at end of file