mirror of
https://github.com/Helium314/HeliBoard.git
synced 2025-04-20 22:29:10 +00:00
add settings to backup
This commit is contained in:
parent
7ae0d0783d
commit
6968488e45
5 changed files with 74 additions and 7 deletions
|
@ -1,5 +1,6 @@
|
||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
apply plugin: 'kotlin-android'
|
apply plugin: 'kotlin-android'
|
||||||
|
apply plugin: 'kotlinx-serialization'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdk 34
|
compileSdk 34
|
||||||
|
@ -78,6 +79,7 @@ dependencies {
|
||||||
implementation 'androidx.preference:preference:1.2.1' // includes appcompat
|
implementation 'androidx.preference:preference:1.2.1' // includes appcompat
|
||||||
implementation 'androidx.recyclerview:recyclerview:1.3.1'
|
implementation 'androidx.recyclerview:recyclerview:1.3.1'
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
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'
|
implementation 'com.github.martin-stone:hsv-alpha-color-picker-android:3.1.0'
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation 'junit:junit:4.13.2'
|
||||||
|
|
|
@ -11,6 +11,7 @@ import android.content.SharedPreferences
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.preference.Preference
|
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.common.FileUtils
|
||||||
import org.dslul.openboard.inputmethod.latin.define.JniLibName
|
import org.dslul.openboard.inputmethod.latin.define.JniLibName
|
||||||
import org.dslul.openboard.inputmethod.latin.settings.SeekBarDialogPreference.ValueProxy
|
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.File
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import java.io.Writer
|
||||||
import java.util.zip.ZipEntry
|
import java.util.zip.ZipEntry
|
||||||
import java.util.zip.ZipInputStream
|
import java.util.zip.ZipInputStream
|
||||||
import java.util.zip.ZipOutputStream
|
import java.util.zip.ZipOutputStream
|
||||||
|
@ -43,6 +48,7 @@ import java.util.zip.ZipOutputStream
|
||||||
* - Debug settings
|
* - Debug settings
|
||||||
*/
|
*/
|
||||||
class AdvancedSettingsFragment : SubScreenFragment() {
|
class AdvancedSettingsFragment : SubScreenFragment() {
|
||||||
|
private val TAG = this::class.simpleName
|
||||||
private var libfile: File? = null
|
private var libfile: File? = null
|
||||||
private val backupFilePatterns by lazy { listOf(
|
private val backupFilePatterns by lazy { listOf(
|
||||||
"blacklists/.*\\.txt".toRegex(),
|
"blacklists/.*\\.txt".toRegex(),
|
||||||
|
@ -166,10 +172,6 @@ class AdvancedSettingsFragment : SubScreenFragment() {
|
||||||
if (backupFilePatterns.any { path.matches(it) })
|
if (backupFilePatterns.any { path.matches(it) })
|
||||||
files.add(file)
|
files.add(file)
|
||||||
}
|
}
|
||||||
if (files.isEmpty()) {
|
|
||||||
infoDialog(requireContext(), R.string.backup_error)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
activity?.contentResolver?.openOutputStream(uri)?.use { os ->
|
activity?.contentResolver?.openOutputStream(uri)?.use { os ->
|
||||||
// write files to zip
|
// write files to zip
|
||||||
|
@ -181,11 +183,14 @@ class AdvancedSettingsFragment : SubScreenFragment() {
|
||||||
fileStream.close()
|
fileStream.close()
|
||||||
zipStream.closeEntry()
|
zipStream.closeEntry()
|
||||||
}
|
}
|
||||||
|
zipStream.putNextEntry(ZipEntry(PREFS_FILE_NAME))
|
||||||
|
zipStream.bufferedWriter().use { settingsToJsonStream(sharedPreferences.all, it) }
|
||||||
zipStream.close()
|
zipStream.close()
|
||||||
}
|
}
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
// inform about every error
|
// 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) }) {
|
if (backupFilePatterns.any { entry!!.name.matches(it) }) {
|
||||||
val file = File(filesDir, entry.name)
|
val file = File(filesDir, entry.name)
|
||||||
FileUtils.copyStreamToNewFile(zip, file)
|
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()
|
zip.closeEntry()
|
||||||
entry = zip.nextEntry
|
entry = zip.nextEntry
|
||||||
|
@ -207,8 +216,13 @@ class AdvancedSettingsFragment : SubScreenFragment() {
|
||||||
}
|
}
|
||||||
val newDictBroadcast = Intent(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION)
|
val newDictBroadcast = Intent(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION)
|
||||||
activity?.sendBroadcast(newDictBroadcast)
|
activity?.sendBroadcast(newDictBroadcast)
|
||||||
|
// reload current prefs screen
|
||||||
|
preferenceScreen.removeAll()
|
||||||
|
onCreate(null)
|
||||||
|
KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(requireContext())
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
// inform about every error
|
// inform about every error
|
||||||
|
Log.w(TAG, "error during restore", t)
|
||||||
infoDialog(requireContext(), requireContext().getString(R.string.restore_error, t.message))
|
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?) {
|
override fun onSharedPreferenceChanged(prefs: SharedPreferences, key: String?) {
|
||||||
if (Settings.PREF_SHOW_SETUP_WIZARD_ICON == key) {
|
if (Settings.PREF_SHOW_SETUP_WIZARD_ICON == key) {
|
||||||
SystemBroadcastReceiver.toggleAppIcon(requireContext())
|
SystemBroadcastReceiver.toggleAppIcon(requireContext())
|
||||||
|
@ -239,3 +297,5 @@ class AdvancedSettingsFragment : SubScreenFragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const val PREFS_FILE_NAME = "preferences.json"
|
||||||
|
|
|
@ -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="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_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_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="restore_error">Erreur lors de la restauration de la sauvegarde : %s</string>
|
||||||
<string name="button_backup">Sauvegarder</string>
|
<string name="button_backup">Sauvegarder</string>
|
||||||
<string name="button_restore">Restaurer</string>
|
<string name="button_restore">Restaurer</string>
|
||||||
|
|
|
@ -165,7 +165,7 @@
|
||||||
<!-- Message for backup and restore dialog -->
|
<!-- Message for backup and restore dialog -->
|
||||||
<string name="backup_restore_message">Save or load from file. Warning: restore will overwrite existing data</string>
|
<string name="backup_restore_message">Save or load from file. Warning: restore will overwrite existing data</string>
|
||||||
<!-- Error message for backup -->
|
<!-- 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 -->
|
<!-- Error message for restore, %s is replaced by the error message -->
|
||||||
<string name="restore_error">Error restoring the backup: %s</string>
|
<string name="restore_error">Error restoring the backup: %s</string>
|
||||||
<!-- backup button -->
|
<!-- backup button -->
|
||||||
|
|
|
@ -9,12 +9,17 @@ buildscript {
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:8.1.2'
|
classpath 'com.android.tools.build:gradle:8.1.2'
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
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
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
// in the individual module build.gradle files
|
// in the individual module build.gradle files
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id 'org.jetbrains.kotlin.plugin.serialization' version '1.9.10' apply false
|
||||||
|
}
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
|
|
Loading…
Add table
Reference in a new issue