add settings to backup

This commit is contained in:
Helium314 2023-11-04 12:05:01 +01:00
parent 7ae0d0783d
commit 6968488e45
5 changed files with 74 additions and 7 deletions

View file

@ -1,5 +1,6 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
android {
compileSdk 34
@ -78,6 +79,7 @@ dependencies {
implementation 'androidx.preference:preference:1.2.1' // includes appcompat
implementation 'androidx.recyclerview:recyclerview:1.3.1'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1" // why not working with 1.6.0?
implementation 'com.github.martin-stone:hsv-alpha-color-picker-android:3.1.0'
testImplementation 'junit:junit:4.13.2'

View file

@ -11,6 +11,7 @@ import android.content.SharedPreferences
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.util.Log
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.preference.Preference
@ -23,9 +24,13 @@ import org.dslul.openboard.inputmethod.latin.SystemBroadcastReceiver
import org.dslul.openboard.inputmethod.latin.common.FileUtils
import org.dslul.openboard.inputmethod.latin.define.JniLibName
import org.dslul.openboard.inputmethod.latin.settings.SeekBarDialogPreference.ValueProxy
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.dslul.openboard.inputmethod.keyboard.KeyboardSwitcher
import java.io.File
import java.io.FileInputStream
import java.io.IOException
import java.io.Writer
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
@ -43,6 +48,7 @@ import java.util.zip.ZipOutputStream
* - Debug settings
*/
class AdvancedSettingsFragment : SubScreenFragment() {
private val TAG = this::class.simpleName
private var libfile: File? = null
private val backupFilePatterns by lazy { listOf(
"blacklists/.*\\.txt".toRegex(),
@ -166,10 +172,6 @@ class AdvancedSettingsFragment : SubScreenFragment() {
if (backupFilePatterns.any { path.matches(it) })
files.add(file)
}
if (files.isEmpty()) {
infoDialog(requireContext(), R.string.backup_error)
return
}
try {
activity?.contentResolver?.openOutputStream(uri)?.use { os ->
// write files to zip
@ -181,11 +183,14 @@ class AdvancedSettingsFragment : SubScreenFragment() {
fileStream.close()
zipStream.closeEntry()
}
zipStream.putNextEntry(ZipEntry(PREFS_FILE_NAME))
zipStream.bufferedWriter().use { settingsToJsonStream(sharedPreferences.all, it) }
zipStream.close()
}
} catch (t: Throwable) {
// inform about every error
infoDialog(requireContext(), R.string.backup_error)
Log.w(TAG, "error during backup", t)
infoDialog(requireContext(), requireContext().getString(R.string.backup_error, t.message))
}
}
@ -199,6 +204,10 @@ class AdvancedSettingsFragment : SubScreenFragment() {
if (backupFilePatterns.any { entry!!.name.matches(it) }) {
val file = File(filesDir, entry.name)
FileUtils.copyStreamToNewFile(zip, file)
} else if (entry.name == PREFS_FILE_NAME) {
val prefLines = String(zip.readBytes()).split("\n")
sharedPreferences.edit().clear().apply()
readJsonLinesToSettings(prefLines)
}
zip.closeEntry()
entry = zip.nextEntry
@ -207,8 +216,13 @@ class AdvancedSettingsFragment : SubScreenFragment() {
}
val newDictBroadcast = Intent(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION)
activity?.sendBroadcast(newDictBroadcast)
// reload current prefs screen
preferenceScreen.removeAll()
onCreate(null)
KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(requireContext())
} catch (t: Throwable) {
// inform about every error
Log.w(TAG, "error during restore", t)
infoDialog(requireContext(), requireContext().getString(R.string.restore_error, t.message))
}
}
@ -231,6 +245,50 @@ class AdvancedSettingsFragment : SubScreenFragment() {
})
}
@Suppress("UNCHECKED_CAST") // it is checked... but whatever (except string set, because can't check for that))
private fun settingsToJsonStream(settings: Map<String, Any?>, out: Writer) {
val booleans = settings.filterValues { it is Boolean } as Map<String, Boolean>
val ints = settings.filterValues { it is Int } as Map<String, Int>
val longs = settings.filterValues { it is Long } as Map<String, Long>
val floats = settings.filterValues { it is Float } as Map<String, Float>
val strings = settings.filterValues { it is String } as Map<String, String>
val stringSets = settings.filterValues { it is Set<*> } as Map<String, Set<String>>
// now write
out.appendLine("boolean settings")
out.appendLine( Json.encodeToString(booleans))
out.appendLine("int settings")
out.appendLine( Json.encodeToString(ints))
out.appendLine("long settings")
out.appendLine( Json.encodeToString(longs))
out.appendLine("float settings")
out.appendLine( Json.encodeToString(floats))
out.appendLine("string settings")
out.appendLine( Json.encodeToString(strings))
out.appendLine("string set settings")
out.appendLine( Json.encodeToString(stringSets))
}
private fun readJsonLinesToSettings(list: List<String>): Boolean {
val i = list.iterator()
val e = sharedPreferences.edit()
try {
while (i.hasNext()) {
when (i.next()) {
"boolean settings" -> Json.decodeFromString<Map<String, Boolean>>(i.next()).forEach { e.putBoolean(it.key, it.value) }
"int settings" -> Json.decodeFromString<Map<String, Int>>(i.next()).forEach { e.putInt(it.key, it.value) }
"long settings" -> Json.decodeFromString<Map<String, Long>>(i.next()).forEach { e.putLong(it.key, it.value) }
"float settings" -> Json.decodeFromString<Map<String, Float>>(i.next()).forEach { e.putFloat(it.key, it.value) }
"string settings" -> Json.decodeFromString<Map<String, String>>(i.next()).forEach { e.putString(it.key, it.value) }
"string set settings" -> Json.decodeFromString<Map<String, Set<String>>>(i.next()).forEach { e.putStringSet(it.key, it.value) }
}
}
e.apply()
return true
} catch (e: Exception) {
return false
}
}
override fun onSharedPreferenceChanged(prefs: SharedPreferences, key: String?) {
if (Settings.PREF_SHOW_SETUP_WIZARD_ICON == key) {
SystemBroadcastReceiver.toggleAppIcon(requireContext())
@ -239,3 +297,5 @@ class AdvancedSettingsFragment : SubScreenFragment() {
}
}
}
private const val PREFS_FILE_NAME = "preferences.json"

View file

@ -181,7 +181,7 @@ Nouveau dictionnaire:
<string name="space_language_slide_summary">Glissez sur la barre d\'espace vers le haut pour changer de langue</string>
<string name="backup_restore_title">Sauvegarde et restauration</string>
<string name="backup_restore_message">Sauvegarde ou chargement à partir d\'un fichier. Attention : la restauration écrasera les données existantes.</string>
<string name="backup_error">Erreur lors de la sauvegarde</string>
<string name="backup_error">Erreur lors de la sauvegarde : %s</string>
<string name="restore_error">Erreur lors de la restauration de la sauvegarde : %s</string>
<string name="button_backup">Sauvegarder</string>
<string name="button_restore">Restaurer</string>

View file

@ -165,7 +165,7 @@
<!-- Message for backup and restore dialog -->
<string name="backup_restore_message">Save or load from file. Warning: restore will overwrite existing data</string>
<!-- Error message for backup -->
<string name="backup_error">Backup error</string>
<string name="backup_error">Backup error: %s</string>
<!-- Error message for restore, %s is replaced by the error message -->
<string name="restore_error">Error restoring the backup: %s</string>
<!-- backup button -->

View file

@ -9,12 +9,17 @@ buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:8.1.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
plugins {
id 'org.jetbrains.kotlin.plugin.serialization' version '1.9.10' apply false
}
allprojects {
repositories {
google()